Herqq

- Main Page
- Related Pages
- Modules
- Namespaces
- Classes
- Files

# Tutorial for Building a UPnP Device

## Setting up the device and service descriptions

Generally, building a UPnP device with HUPnP involves two main steps in your part. First, you have to define a UPnP device description document following the specifications set by the UPnP forum. Depending of your UPnP Device Description document, you may need to define one or more UPnP service description documents as well. Second, you may have to implement a class for your device and most often one or more classes for each service your device contains.

For example, if you want to implement a standard UPnP device named BinaryLight:1, your device description could look something like this:

     <?xml version="1.0"?>
     <root xmlns="urn:schemas-upnp-org:device-1-0">
         <specVersion>
             <major>1</major>
             <minor>0</minor>
         </specVersion>
         <device>
             <deviceType>urn:schemas-upnp-org:device:BinaryLight:1</deviceType>
             <friendlyName>UPnP Binary Light</friendlyName>
             <manufacturer>MyCompany</manufacturer>
             <manufacturerURL>www.mywebsite.org</manufacturerURL>
             <modelDescription>New brilliant BinaryLight</modelDescription>
             <modelName>SuperWhiteLight 4000</modelName>
             <modelNumber>1</modelNumber>
             <UDN>uuid:138d3934-4202-45d7-bf35-8b50b0208139</UDN>
             <serviceList>
                 <service>
                     <serviceType>urn:schemas-upnp-org:service:SwitchPower:1</serviceType>
                     <serviceId>urn:upnp-org:serviceId:SwitchPower:1</serviceId>
                     <SCPDURL>switchpower_scpd.xml</SCPDURL>
                     <controlURL>/control</controlURL>
                     <eventSubURL>/eventing</eventSubURL>
                 </service>
             </serviceList>
         </device>
     </root>

Note that the above is the standard device template for UPnP BinaryLight:1 filled with imaginary information.

Since the BinaryLight:1 defines a service, SwitchPower:1, you have to provide a service description document that could look like this:

     <?xml version="1.0"?>
     <scpd xmlns="urn:schemas-upnp-org:service-1-0">
         <specVersion>
             <major>1</major>
             <minor>0</minor>
         </specVersion>
         <actionList>
             <action>
                 <name>SetTarget</name>
                 <argumentList>
                     <argument>
                         <name>newTargetValue</name>
                         <relatedStateVariable>Target</relatedStateVariable>
                         <direction>in</direction>
                     </argument>
                 </argumentList>
              </action>
              <action>
                  <name>GetTarget</name>
                  <argumentList>
                      <argument>
                          <name>RetTargetValue</name>
                          <relatedStateVariable>Target</relatedStateVariable>
                          <direction>out</direction>
                      </argument>
                  </argumentList>
              </action>
              <action>
                  <name>GetStatus</name>
                  <argumentList>
                      <argument>
                          <name>ResultStatus</name>
                          <relatedStateVariable>Status</relatedStateVariable>
                          <direction>out</direction>
                      </argument>
                  </argumentList>
              </action>
          </actionList>
          <serviceStateTable>
              <stateVariable sendEvents="no">
                  <name>Target</name>
                  <dataType>boolean</dataType>
                  <defaultValue>0</defaultValue>
              </stateVariable>
              <stateVariable sendEvents="yes">
                  <name>Status</name>
                  <dataType>boolean</dataType>
                  <defaultValue>0</defaultValue>
              </stateVariable>
          </serviceStateTable>
     </scpd>

The above description is the standard service description for the SwitchPower:1 without any vendor specific declarations. For more information about description documents, see the UDA specification, sections 2.3 and 2.5.

## Creating the necessary HUPnP classes

HUPnP doesn't require any classes to be created in order to "host" a UPnP device (make it visible for UPnP control points), but in order to plug in custom functionality you often have to accompany the device and service descriptions with corresponding classes.

In our example we have to derive a class we have to derive a class from Herqq::Upnp::HServerService for the SwitchPower:1 service description and we can derive a class from Herqq::Upnp::HServerDevice for the BinaryLight:1 device description. Note the last point, we do not have to create a class for the BinaryLight:1, but we can. Furthermore, if your service has no actions you do not need to create your own HServerService type either.

Note:
  Omitting the creation of custom HServerDevice classes is common if there is no need to define a type that orchestrates the use, initialization and control of the contained services. Furthermore, such a type can specify information about the embedded devices and services that have to be present for the type to be initialized properly. And of course, it can offer features that are not present in the default HServerDevice class.

To create a custom Herqq::Upnp::HServerDevice you only need to derive from it. There are no abstract member functions to override, but there are a few virtual member functions that could be very useful to override in case you are writing a type for other people to use. For more information of this, see Device Model.

To create a concrete class from Herqq::Upnp::HServerService that exposes custom actions you can either:

- Override Herqq::Upnp::HServerService::createActionInvokes(), which purpose is to create callable entities that will be called when the corresponding UPnP actions are invoked.
- Define `Q_INVOKABLE` methods in your custom type derived from HServerService using the same method names as the action definitions in the service description document.

The first option is much more flexible, as you have full control over what HUPnP should call when a particular action is invoked. In addition, callable entities aren't tied to member functions. The second option may be more convenient, as you don't have to implement HServerService::createActionInvokes() and create the callable entities by yourself. Whichever option you choose, every action implementation has to have a signature of `action(const HActionArguments&, HActionArguments*)` and `int` as a return type.

Note:
  - The UPnP actions of a particular UPnP service are defined in the service's description file and your service implementation has to implement all of them.
  - The callable entities are used internally by HUPnP. HUPnP does not otherwise expose them directly in the public API for action invocation.

To continue with the example we will create two classes, one for the BinaryLight:1 and one for the SwitchPowerService:1. Note, the class for the BinaryLight:1 is not required, but it is done here for demonstration purposes. Also note that for this example the class declarations are put into the same header file, although in real code you might want to separate them.

`mybinarylight.h`

     #include <HUpnpCore/HServerDevice>
     #include <HUpnpCore/HServerService>

     class MyBinaryLightDevice :
        public Herqq::Upnp::HServerDevice
     {

     public:

        MyBinaryLightDevice();
        virtual ~MyBinaryLightDevice();
     };

     class MySwitchPowerService :
        public Herqq::Upnp::HServerService
     {
     protected:

         virtual HActionInvokes createActionInvokes();

     public:

         MySwitchPowerService();
         virtual ~MySwitchPowerService();
     };

In turn, the implementation could look something like this:

`mybinarylight.cpp`

     #include "mybinarylight.h"

     using namespace Herqq::Upnp;

     MyBinaryLightDevice::MyBinaryLightDevice()
     {
     }

     MyBinaryLightDevice::~MyBinaryLightDevice()
     {
     }

     MySwitchPowerService::MySwitchPowerService()
     {
     }

     MySwitchPowerService::~MySwitchPowerService()
     {
     }

     HServerServer::HActionInvokes MySwitchPowerService::createActionInvokes()
     {
         HActionInvokes retVal;
         return retVal;
     }

Those who know UPnP and paid close attention to the above example might have noticed that something was off. Where are the actions?

According to the UPnP Device Architecture (UDA), a service may have zero or more actions. If a service has no actions, you don't have to create a custom HServerService derivative in the first place, but even if you do, similar class declaration and definition as shown above are enough.

However, the standard BinaryLight:1 device type specifies the SwitchPower:1 service type that has three actions defined (look back in the service description document). Namely these are SetTarget, GetTarget and GetStatus. To make the example complete the `MySwitchPowerService` class requires some additional work. Note that next example shows only one way of making the service complete. There are a few other ways, which will be discussed later in depth.

The complete declaration for `MySwitchPowerService`:

`mybinarylight.h`

     #include <HUpnpCore/HServerService>

     class MySwitchPowerService :
        public Herqq::Upnp::HServerService
     {
     protected:

         virtual HActionInvokes createActionInvokes();

     public:

         MySwitchPowerService();
         virtual ~MySwitchPowerService();

         qint32 setTarget(
             const Herqq::Upnp::HActionArguments& inArgs,
             Herqq::Upnp::HActionArguments* outArgs);

         qint32 getTarget(
             const Herqq::Upnp::HActionArguments& inArgs,
             Herqq::Upnp::HActionArguments* outArgs);

         qint32 getStatus(
             const Herqq::Upnp::HActionArguments& inArgs,
             Herqq::Upnp::HActionArguments* outArgs);
     };

The complete definition for `MySwitchPowerService`:

`mybinarylight.cpp`

     #include "mybinarylight.h"

     #include <HUpnpCore/HServerAction>
     #include <HUpnpCore/HActionArguments>
     #include <HUpnpCore/HServerStateVariable>

     MySwitchPowerService::MySwitchPowerService()
     {
     }

     MySwitchPowerService::~MySwitchPowerService()
     {
     }

     HServerService::HActionInvokes MySwitchPowerService::createActionInvokes()
     {
         Herqq::Upnp::HServerService::HActionInvokes retVal;

         retVal.insert(
             "SetTarget",
             Herqq::Upnp::HActionInvoke(this, &MySwitchPowerService::setTarget));

         // The above lines map the MySwitchPowerService::setTarget() method to
         // the action that has the name SetTarget. In essence, this mapping instructs
         // HUPnP to call this method when the SetTarget action is invoked.
         // However, note that HActionInvoke accepts any "callable entity",
         // such as a normal function or a functor. Furthermore, if you use a
         // method the method does not have to be public.

         retVal.insert(
             "GetTarget",
             Herqq::Upnp::HActionInvoke(this, &MySwitchPowerService::getTarget));

         retVal.insert(
             "GetStatus",
             Herqq::Upnp::HActionInvoke(this, &MySwitchPowerService::getStatus));

         return retVal;
     }

     qint32 MySwitchPowerService::setTarget(
         const Herqq::Upnp::HActionArguments& inArgs, Herqq::Upnp::HActionArguments* outArgs)
     {
         Herqq::Upnp::HActionArgument newTargetValueArg = inArgs.get("newTargetValue");
         if (!newTargetValueArg.isValid())
         {
             // If MySwitchPowerService class is not made for direct public use
             // this check is redundant, since in that case this method is called only by
             // HUPnP and HUPnP always ensures that the action arguments defined in the
             // service description are present when an action is invoked.

             return Herqq::Upnp::UpnpInvalidArgs;
         }

         bool newTargetValue = newTargetValueArg.value().toBool();
         stateVariables().value("Target")->setValue(newTargetValue);

         // The above line modifies the state variable "Target", which reflects the
         // "target state" of a light device, i.e. if a user wants to turn off a light, the
         // "target state" is the light turned off whether the light can be turned
         // off or not.

         //
         // Do here whatever that is required to turn on / off the light
         // (set it to the target state)
         //

         //
         // If it succeeded, we should modify the Status state variable to reflect
         // the new state of the light.
         //

         stateVariables().value("Status")->setValue(newTargetValue);

         return Herqq::Upnp::UpnpSuccess;
     }

     qint32 MySwitchPowerService::getTarget(
         const Herqq::Upnp::HActionArguments& inArgs, Herqq::Upnp::HActionArguments* outArgs)
     {
         if (!outArgs)
         {
             // See the comments in MySwitchPowerService::setTarget why this
             // check is here. Basically, this check is redundant if this method
             // is called only by HUPnP, as HUPnP ensures proper arguments
             // are always provided when an action is invoked.

             return Herqq::Upnp::UpnpInvalidArgs;
         }

         bool b = stateVariables().value("Target")->value().toBool();
         outArgs->setValue("RetTargetValue", b);

         return Herqq::Upnp::UpnpSuccess;
     }

     qint32 MySwitchPowerService::getStatus(
         const Herqq::Upnp::HActionArguments& inArgs, Herqq::Upnp::HActionArguments* outArgs)
     {
         if (!outArgs)
         {
             // See the comments in MySwitchPowerService::getTarget();
             return UpnpInvalidArgs;
         }

         bool b = stateVariables().value("Status")->value().toBool();
         outArgs->setValue("ResultStatus", b);

         return Herqq::Upnp::UpnpSuccess;
     }

The above example overrode the HServerService::createActionInvokes() and did the action name - callable entity mapping. However, if you'd rather have HUPnP do that automatically, you can mark your action implementations as `Q_INVOKABLE` as follows:

`mybinarylight.h`

     #include <HUpnpCore/HServerService>

     class MySwitchPowerService :
        public Herqq::Upnp::HServerService
     {
     Q_OBJECT

     public:

         MySwitchPowerService();
         virtual ~MySwitchPowerService();

         Q_INVOKABLE qint32 SetTarget(
             const Herqq::Upnp::HActionArguments& inArgs,
             Herqq::Upnp::HActionArguments* outArgs);

         Q_INVOKABLE qint32 GetTarget(
             const Herqq::Upnp::HActionArguments& inArgs,
             Herqq::Upnp::HActionArguments* outArgs);

         Q_INVOKABLE qint32 GetStatus(
             const Herqq::Upnp::HActionArguments& inArgs,
             Herqq::Upnp::HActionArguments* outArgs);
     };

Apart from changing the method names to start with capital letters, the method definitions stay otherwise the same.

Note:
  Using `Q_INVOKABLE` methods as action implementations you have to ensure that the names of the member functions correspond exactly to the action names defined in the service description document.

### Some closing notes

First of all, you may want to skim the discussion in Device Model and Device Hosting to fully understand the comments in the example above. Especially the section Setting Up the Device Model is useful if you want to learn the details of building a custom UPnP device using HUPnP. That being said, perhaps the most important issues of building a custom UPnP device using HUPnP can be summarized to:

- Every device description has to have a corresponding Herqq::Upnp::HServerDevice and every service description has to have a corresponding Herqq::Upnp::HServerService. However, you don't have to create a custom HServerDevice, in which case HUPnP will create and use a default HServerDevice type. In addition, if a service has no actions you don't have to create a custom HServerService. On the other hand, most commonly a service has one or more actions, so this is something you'll be doing often.

- It is perfectly fine to create custom HServerDevice and HServerService classes just to be hosted in a Herqq::Upnp::HDeviceHost. Such classes exist only to run your code when UPnP control points interact with them over the network. These types of classes are to be used directly only by HDeviceHost.

- You can create more advanced HServerDevice and HServerService classes perhaps to build a higher level public API or just to provide yourself a nicer interface for doing something. This was the case with `MySwitchPowerService` class, which extended the HServerService interface by providing the possibility of invoking the actions of the service through the `setTarget()`, `getTarget()` and `getStatus()` methods.

- HUPnP allows direct (in-process) access to the hosted HServerDevice and HServerService classes, which means you can interact with your classes while they are being hosted and possibly used from external processes. Custom HServerDevice and HServerService interfaces may be beneficial in such a case.

- The type behind an Herqq::Upnp::HActionInvoke can hold any callable entity, such as a pointer to a normal function, functor or a pointer to a member function.

- A public callable entity should always strictly verify the input and respond to illegal input accordingly. A "private" callable entity that is called only by HUPnP can rest assured that HUPnP never passes a null input argument or an argument that has an incorrect name or data type.

- Before implementing your own device and service types directly from HServerDevice and HServerService, you should check if HUPnP provides more refined classes to suit your requirements. For instance, HUPnP provides a base class Herqq::Upnp::Lighting::HAbstractSwitchPower for simplifying the implementation and use of SwitchPower:1.

In any case, the above example demonstrates a fully standard-compliant implementation of BinaryLight:1. The next step is to publish your HServerDevice in the network for UPnP control points to discover. You can find the instructions for that in HDeviceHost and Device Hosting.