UPnP tutorial using Girder

This post will try to explain how the UPnP devices can be controlled using Girder. By explaining some UPnP basics and at the same time create some example code to control a simple device.



[ad name=”468×60 Banner”]

About UPnP devices

A UPnP device is an abstract representation of a device, with a certain hierarchy of services, variables, methods and devices.

  • Each device can have services and embedded (sub)devices
  • Each service can have statevariables and methods
  • Each method can have arguments, these arguments are always associated with a statevariable

From a control perspective there are 2 types of UPnP capable structures;

  1. Devices; these expose all sorts of information about themselves (as mentioned above)
  2. Controlpoints; these collect information about devices and allow its user to control the devices

In physical appliances the two can be combined. For example, a TV set acts as a UPnP media player/renderer device (UPnP device) but it also allows you to browse a remote UPnP media server and play it on the local UPnP renderer (the TV itself), and as such is a control point.


Tutorial start

Start the Software Light and Device Spy (utility from the developer tools). The device spy should now display the software light and you should be able to browse its properties, services, methods, etc. Now start Girder and the UPnP-2-xPL gateway, then use the Girder variable inspector (press F12) and refresh the display, when you now browse to UPnP.devices, you should get a similar tree to the Device Spy tree.

So what has happened so far;

  • The software light uses the UPnP protocol to announce itself on the network to UPnP capable controlpoints
  • The UPnP-2xPL gateway is a UPnP controlpoint and collects information about all UPnP devices on the network. At the same time it is a xPL device and it will transmit the received UPnP device information onto the xPL network to xPL clients.
  • The Girder xPLGirder component is an xPL client that collects the UPnP information from the UPnP-2-xPL gateway and stores it in a table for Girder to access and control it.

So, through this route; Software Light -> UPnP protocol -> UPnP-2-xPL gateway -> xPL protocol -> xPLGirder, Girder now has all the information required to control the device. The image below shows the Girder variable inspector tree with the matching elements in the Device Spy.

Matching elements between Girder and Device Spy

To access the information in Girder there are 2 ways;

  1. traverse the hierarchical device tree from the UPnP.devices table, or
  2. directly accessing elements by their ID, through the UPnP.IDlist table.

The gateway dissects the UPnP devices into their underlying components and provides each with an ID to reference. This ID is not persistent, it is a sequential number provided by the gateway and may differ from one session to the next. Each UPnP element within Girder has the following properties regarding this ID;

  • ID; the ID of the element
  • parent; the ID of the parent that an element belongs to, only top-level devices (root-devices) do not have a parent property.
  • IDlist; list of child IDs

See the image below; red shows the parent-relationship, blue the IDlist with child relationship, green shows the service relation to its methods and statevariables.

Parent-child relationships

[ad name=”468×60 Banner”]

To consistently control a device, we must be capable of uniquely identifying the same device over session, for this we’ll use the device UUID (unique and according to the UPnP protocol always the same). There is only one issue with it; Girder cannot display variables in the variable inspector that have either a ‘.’ (dot) or ‘:’ (colon) in their name. So the xPLGirder component will replace those with ‘_’ (underscores) to make sure we can visually browse the devices and its properties in Girder.

Now create a new text file called ‘networklight.lua’ and add the following;

-- Setup persistent device adn service IDs
local light = UPnP.device["<copied ID>"]
local power = light.services["<copied ID>"]
local dimmer = light.services["<copied ID>"]

In Girder right-click the UUID of the the device in the device table (not the deviceid property inside the table!) and select ‘Copy variable name’ from the menu. Now in the created lua file paste it over the ‘<copied ID>’ value in the ‘local light = ..’ line. Repeat this for the SwitchPower and DimmerService services.

Get the unique ID's

Now copy the code from the below example, but do not overwrite the ID’s you just copied;

-- Setup persistent device and service IDs
local light = UPnP.devices["c910a273-d553-4c8a-92b0-e62ddb60fd46"]
local dimmer = light.services["urn_upnp-org_serviceId_DimmingService_0001"]
local power = light.services["urn_upnp-org_serviceId_SwitchPower_0001"]

-- define global and methods to export
Light = {

 On = function (self)
 return power.methods.SetTarget:execute(true)

 Off = function (self)
 return power.methods.SetTarget:execute(false)

 Dim = function (self, level)
 return dimmer.methods.SetLoadLevelTarget:execute(level)

 Status = function (self)
 local load = dimmer.variables.LoadLevelStatus.value .. "%"
 if power.variables.Status.value == "False" then
 print("Light is currently off.")
 print("Light is currently on at " .. load)

Now lets step through this code. The first lines with the ID’s make sure that the correct reference to the services we need are available. Then the global table ‘Light’ gets defined with several functions/methods. To make the device do what you want, or get the information that you need, you need to understand how to use a devices methods and variables to get that information.

[ad name=”468×60 Banner”]

Some generics/conventions about UPnP

(in Device Spy navigate to the SwitchPower service)

Variables may or may not be evented. That is an evented variable will inform any subscribed controlpoint of changes in value, whereas as non-evented variable will not do this. If a varaible is non-evented, then the common convention to get the value is through a method called ‘Get<varname>’. In this case there is the ‘Target’ statevariable in the SwitchPower service, adn if you examine its properties you’ll see it is non-evented. But in the methodlist there is a method called ‘GetTarget’, which still enables you to get the value (the naming convention also extends to ‘Set’ for setting a value, in this case ‘SetTarget’). Using the device spy you can check the variables for being evented or not, and the methods for ‘Get’ing and ‘Set’ing.

Eventhough a power switch is either On or Off, the SwitchPower service still has 2 state variables; Target and Status. This is rather common with UPnP devices. Status will hold the current value, while Target holds the value, to which the device is currently working. For a powerswitch this may seem redundant, but for a valve for example it would not be. For example a valve that takes 1 minute to completely open. The same for the dimmer, if the dimmer is set to slowly brighten over a period of 30 minutes, the target maybe 100%, while the actual value will only slowly progress towards the 100%.

Methods take 0 or more parameters, each of them may be defined as direction IN (provided to the method) or direction OUT (returned from the method). Additionally (and optional) one of the OUT parameters may be set as the main return value (RetVal), but this is often not set. The order for method arguments is defined; first all the IN parameters, then RetVal, and finally all other OUT parameters. If you scan the methods in Device Spy, you’ll see that the ‘Return argument’ property mostly has value ‘<none>’ indicating that RetVal has not been set.

As a definition for method arguments, the statevariables are reused. Most methods deal with one or more statevariables anyway, so by coupling the arguments with a statevariable, suddenly the type and allowed values for the argument are pretty well defined. In some cases there are no related statevariables for a method and then a special statevariable needs to be created solely for this purpose. Those statevariable names must start with ‘A_ARG_TYPE_’ by convention. For the same matter; any statevariables or methods that start with ‘X_’ are vendor specific extensions to standard UPnP services. In Device Spy the methods have a list of properties, numbered per argument; ‘Argument x’, which contains the basic type and the argument name, and ‘Argument x ASV’ which contains the name of the associated statevariable. For example, method SetLoadLevelTarget of the DimmingService has ‘Argument 1 ASV’ set to LoadLevelTarget, if you now lookup the statevariable LoadLevelTarget, you’ll see that this is an ‘ui1’ (unsigned integer) type, with a valid range from 0 to 100.

[ad name=”468×60 Banner”]

Understanding the code

Now lets start with the easiest commands; the On() and Off() methods in our sample code. We need to control the SwitchPower service and set a new Target value. If you check the contents of the SetTarget method, here the ASV is the statevariable Target, which simply reads as a boolean type. Both methods require a single argument, IN, no arguments defined as OUT. So the overall code is simply from the service (power) select the correct method (methods.SetTarget this is 1-on-1 with the UPnP defined name) and call ‘execute’ on it using the correct number of arguments; power.methods.SetTarget:execute(false)

On to the next method; Dim(). Its based on the dimmer service and its method ‘SetLoadLevelTarget’, basically using variable LoadLevel, with the generic conventions applied and hence the pre-fix ‘Set’ and the post-fix ‘Target’, as explained above. If you browse it using Device Spy, and also the ASV LoadLevel, you’ll see its an unsigned integer with a range from 0 to 100. As with the On() and Off() commands, all we need to do is call the right method with the new level to be set.

The last method in our sample code reports back the current status and prints it in the Girder lua console. The statevariables Status (on the SwitchPower service) and LoadLevelStatus (on the DimmingService service) are both evented. So whenever their value changes any clients will be informed. xPLGirder will automatically update the value of the variable in the hierarchical device/service tree. Hence it will always contain the latest value (but only if they are evented!!). Now simply collect the value from the LoadLevel variable, combine it with the Status of the SwitchPower and construct the result to be printed.

Something about conversions;

All data is transferred as strings (both for xPL as well as UPnP). So in the example, the On() and Off() call the SetTarget method with a lua boolean value, which will automatically be converted to a string. The return values, the value of the variable ‘power.variables.Status.value’ in the example code is a string value that reads “True” or “False” it will not automatically be transferred back to a lua boolean. Its the same for the IDs used, they are all strings even though they contain numerical values. So looking up an element in the IDlist must be done using strings; local myElem = UPnP.IDlist(“15”).

Controlling the light

Now save your code. Create a new Girder Script action and copy the full contents of the file created into this new script action. Now execute the action once. If you now refresh the variable inspector, you should see the global ‘Light’ table and if you expand it, the methods inside; On(), Off(), Dim() and Status().

The created Light device

In the Girder lua console type ‘Light:On()’ and press enter to turn the light on. ‘Light:Off()’ to switch it off again. Use ‘Light:On()’ and ‘Light:Dim(50)’ to switch it to 50%.

If this doesn’t work, make sure you’ve got all components running properly. Also note that the Network Light included with the Developer tools for UPnP does change its UUID every time it starts, so every time you restart the light, you should update your code to the new UUID. The updated version of the light does not have this issue.


I hope this was helpful, and I’d welcome some feedback for improvements. If you need help, please post on the Girder forum.


[ad name=”468×60 Banner”]


Leave a Reply

Your email address will not be published. Required fields are marked *


This site uses Akismet to reduce spam. Learn how your comment data is processed.

Subscribe without commenting