Universal Plug and Play

UPnP Control And Eventing Tutorial And User Guide

Introduction

This document shows how to build applications and devices that can be controlled by control points via SOAP and that publish event messages via GENA.

The SOAP protocol is implemented in the UPnP/SOAP library. The GENA protocol is implemented in the UPnP/GENA library. Both libraries require the RemotingNG library to provide basic common infrastructure.

Creating UPnP devices and services is a multi-step process. The first step is to define the services that the device will support. In most cases, this will be standard services (Standardized Device Control Protocols, or DCPs) defined by the UPnP Forum. The specifications and service definitions for these DCPs can be downloaded from the UPnP Forum website.

Before implementing a standard service, we recommend familiarizing yourself with the documentation available with the DCP.

Alternatively, you can also define your own devices and services. In this case, the first step is to write a XML service description for the service. Please refer to the UPnP Device Architecture (PDF) document, section 2.3, for information on how this is done.

When the service description documents for your device are available, the second step is to invoke the UPnP C++ code generator (UPnPGen) to create C++ classes with Remoting annotations. The generated header files will then be fed to the RemotingNG code generator, which will generate the necessary Remoting infrastructure code for device control with SOAP and eventing with GENA.

The third step is implementing the code for the UPnP services - the code that gets executed when a control point invokes actions on the controlled device.

Finally, as fourth step, some infrastructure code must be written to initialize the UPnP framework, set up the web server, register the services, set up eventing, etc.

The UPnP Code Generator (UPnPGen)

The UPnPGen utility generates C++ code from UPnP XML service descriptions. For every service description that is fed into UPnPGen, the utility creates a C++ class with a header file and an implementation file. The header file contains Remoting annotations and in a later step will be fed into the Remoting code generator to generate the necessary infrastructure code for network communication via SOAP and GENA.

Code Generator Configuration

The UPnPGen utility requires an XML configuration file as input. This file specifies which UPnP service description files to read, where to find the service description files, and where to store the resulting C++ header and implementation files, among other things. The path to the XML configuration file is passed as argument to UPnPGen. Starting UPnPGen without any arguments will display a help screen.

A sample for such a configuration file is shown below.

<AppConfig>
    <UPnPGen>
        <input>
            <services>
                <service>
                    <type>urn:schemas-upnp-org:service:Dimming:1</type>
                    <description>xml/service/Dimming1.xml</description>
                    <class>Dimming</class>
                    <implClass>DimmingImpl</implClass>
                </service>
                <service>
                    <type>urn:schemas-upnp-org:service:SwitchPower:1</type>
                    <description>xml/service/SwitchPower1.xml</description>
                    <class>SwitchPower</class>
                    <implClass>SwitchPowerImpl</implClass>
                </service>
            </services>
        </input>
        <output>
            <namespace>UPnPS::LightingControls1</namespace>
            <include>include/UPnPS/LightingControls1</include>
            <src>src</src>
            <library>LightingControls1</library>
            <alwaysInclude>UPnPS/LightingControls1/LightingControls1.h</alwaysInclude>
            <copyright>Copyright (c) 2011, Applied Informatics Software Engineering GmbH.
                All rights reserved.

                This is unpublished proprietary source code of Applied Informatics.
                The contents of this file may not be disclosed to third parties, 
                copied or duplicated in any form, in whole or in part.
            </copyright>
        </output>
    </UPnPGen>
</AppConfig>

Following is a description of all XML elements supported in the configuration file.

AppConfig

The XML root or document element. The name of this element is not relevant. A typical element name is AppConfig, but any other valid element name would do as well.

UPnPGen

This element is required. Its child elements contain the configuration data for the code generator.

input/services/service

For every UPnP service description document, a service element must be present. Under the service element, type, description, class and implClass elements must be given.

input/services/service/type

This specifies the UPnP service type, as defined for the service by the UPnP Forum. For services not defined by the UPnP Forum, an appropriate service type in the form urn:<domain-name>:service:<serviceType>:<version> must be created (e.g., urn:schemas-appinf-com:service:SampleService:1).

input/services/service/description

This specifies the path in the filesystem (absolute or relative) where the XML service description document can be found.

input/services/service/class

This specifies the name of the interface class generated from the service description. Must be a valid C++ class name. The interface class will contain a pure virtual member function for every action defined in the service definition.

input/services/service/implClass

This optional element specifies the name of the implementation class generated from the service description. Must be a valid C++ class name. The implementation class will be a subclass of the interface class and provides concrete implementations of all action methods defined in the interface class.

input/services/service/eventSuffix

This optional element allows to change the name of event variables in the interface class for the respective service. The default suffix is "Changed", so an UPnP event named "IsRamping" would be named "isRampingChanged" in the interface class. For some UPnP services, the default name can lead to a naming conflict, resulting in a method and an event with the same name. To resolve this conflict, specify a different suffix, e.g. "ChangedEvent".

Note that this option overrides the output/eventSuffix option.

output/namespace

This element specifies the C++ namespace, where generated classes will be in. If not specified, all generated classes will be in the top-level namespace. Can be a nested namespace (e.g., Sample::Service).

Example:

<namespace>Sample::Service</namespace>

output/include

This element specifies the directory where header files for classes generated by the code generator are written to. The directory is automatically created if it does not already exist.

output/src

This element specifies the directory where implementation files for classes generated by the code generator are written to. The directory is automatically created if it does not already exist.

output/library

If this optional element is specified, the code generated by the code generator is assumed to be exported from a (shared) library. For this to work with Windows DLLs, the classes must be defined with a __declspec directive. Specifying a library name will direct the code generator to create all class definitions in the form:

class Library_API IService
{
    ...
};

where Library in Library_API is replaced with the library name given in this element. For this to work, the Library_API macro must be defined somewhere.

The definition for this macro typically looks like this:

#if defined(_WIN32) && defined(POCO_DLL)
    #if defined(Library_EXPORTS)
        #define Library_API __declspec(dllexport)
    #else
        #define Library_API __declspec(dllimport)    
    #endif
#endif


#if !defined(Library_API)
    #define Library_API
#endif

output/alwaysInclude

This element instructs the code generator to always include the header file given as content.

Example:

<alwaysInclude>Sample/Service/Sample.h</alwaysInclude>

will instruct the compiler to include a

#include "Sample/Service/Sample.h"

directive in each header file it generates.

output/copyright

This specifies a copyright (or other) message that will be added to the header comment section of each generated header and implementation file.

output/eventSuffix

This optional element allows to change the name of event variables in the interface classes for the all services. The default suffix is "Changed", so an UPnP event named "IsRamping" would be named "isRampingChanged" in the interface class. For some UPnP services, the default name can lead to a naming conflict, resulting in a method and an event with the same name. To resolve this conflict, specify a different suffix, e.g. "ChangedEvent".

output/timestamps

Enable (default) or disable timestamps in generated C++ header and implementation files. Valid values are true to enable (default) and false to disable timestamps. If enabled, every generated header and implementation file will include the date and time when the file was generated in its header comment block.

Invoking The Code Generator

Command Line Usage

The code generator is invoked by running the UPnPGen executable. The configuration file must be passed as argument. It is possible to specify more than one configuration file in the argument list. In this case, settings specified in later files replace settings in earlier ones.

Command Line Options

The UPnP code generator utility supports the following command line options:

  • /help, --help, -h: Display help information on command line arguments.
  • /define:<name>=<value>, --define=<name>=<value>, -D<name>=<value>: Define a configuration property. A configuration property defined with this option can be referenced in the configuration file, using the following syntax: ${<name>}.
  • /interface, --interface, -i: Generate the interface class. This is the default.
  • /implementation, --implementation, -m: Generate the implementation class.
  • /osp, --osp, -o: Generate the service interface class as subclass of Poco::OSP::Service.

Command line arguments must be specified using the correct option syntax for the respective platform.

For example, on Windows, one would use:

UPnPGen /interface UPnPGen.xml

whereas, on a Unix or Linux platform, one would use:

UPnPGen --interface UPnPGen.xml

or, using the abbreviated options format (create the implementation class only):

UPnPGen -m UPnPGen.xml

Using Variables in The Configuration File

The code generator configuration file can contain references to variables (or configuration properties) in the form ${<variable>}. Variables can be defined directly in the configuration file, directly under the XML root element, or they can be passed using a command line option. System property values from Poco::Util::SystemConfiguration can be used as well.

As an example, we can use the output/copyright element to not just include a copyright message, but also include the date and time when the respective file has been generated:

<output>
    ...
    <copyright>This file has been generated on ${system.dateTime}.
    Copyright (c) 2011 ...</copyright>
    ...
</output>

Classes And Files Generated by The Code Generator

The UPnP code generator utility generates interface and implementation classes.

Interface Class

The interface class contains a pure virtual member function for every action defined in the service definition. It also contains public event members for all evented state variables defined in the service definition. The interface class definition contains annotations for the Remoting code generator. For example the interface class generated for the UPnP Dimming service looks as follows. For brevity, some member functions have been omitted.

//@ namespace="urn:schemas-upnp-org:service:Dimming:1"
//@ remote
class LightingControls1_API Dimming
{
public:
    typedef Poco::SharedPtr<Dimming> Ptr;

    Dimming();
        /// Creates a Dimming.

    virtual ~Dimming();
        /// Destroys the Dimming.

    //@ name=GetIsRamping
    //@ optional=true
    //@ replyName=GetIsRampingResponse
    //@ retIsRamping={name="retIsRamping", direction=out}
    virtual void getIsRamping(bool& retIsRamping) = 0;

    //@ name=GetLoadLevelStatus
    //@ optional=true
    //@ replyName=GetLoadLevelStatusResponse
    //@ retLoadlevelStatus={name="retLoadlevelStatus", direction=out}
    virtual void getLoadLevelStatus(Poco::UInt8& retLoadlevelStatus) = 0;

    //@ getLoadlevelTarget={name="GetLoadlevelTarget", direction=out}
    //@ name=GetLoadLevelTarget
    //@ optional=true
    //@ replyName=GetLoadLevelTargetResponse
    virtual void getLoadLevelTarget(Poco::UInt8& getLoadlevelTarget) = 0;

    //@ name=SetLoadLevelTarget
    //@ newLoadlevelTarget={name="newLoadlevelTarget"}
    //@ optional=true
    //@ replyName=SetLoadLevelTargetResponse
    virtual void setLoadLevelTarget(Poco::UInt8 newLoadlevelTarget) = 0;

    //@ name=SetRampRate
    //@ newRampRate={name="newRampRate"}
    //@ optional=true
    //@ replyName=SetRampRateResponse
    virtual void setRampRate(Poco::UInt8 newRampRate) = 0;

    //@ name=SetStepDelta
    //@ newStepDelta={name="newStepDelta"}
    //@ optional=true
    //@ replyName=SetStepDeltaResponse
    virtual void setStepDelta(Poco::UInt8 newStepDelta) = 0;

    //@ name=StartRampDown
    //@ optional=true
    //@ replyName=StartRampDownResponse
    virtual void startRampDown() = 0;

    //@ name=StartRampToLevel
    //@ newLoadLevelTarget={name="newLoadLevelTarget"}
    //@ newRampTime={name="newRampTime"}
    //@ optional=true
    //@ replyName=StartRampToLevelResponse
    virtual void startRampToLevel(Poco::UInt8 newLoadLevelTarget, Poco::UInt32 newRampTime) = 0;

    //@ name=StartRampUp
    //@ optional=true
    //@ replyName=StartRampUpResponse
    virtual void startRampUp() = 0;

    //@ name=StepDown
    //@ optional=true
    //@ replyName=StepDownResponse
    virtual void stepDown() = 0;

    //@ name=StepUp
    //@ optional=true
    //@ replyName=StepUpResponse
    virtual void stepUp() = 0;

    //@ name=StopRamp
    //@ optional=true
    //@ replyName=StopRampResponse
    virtual void stopRamp() = 0;

    //@ name=IsRamping
    //@ oneway=true
    //@ optional=true
    Poco::BasicEvent<const bool> isRampingChanged;
    //@ name=LoadLevelStatus
    //@ oneway=true
    Poco::BasicEvent<const Poco::UInt8> loadLevelStatusChanged;
    //@ name=RampPaused
    //@ oneway=true
    //@ optional=true
    Poco::BasicEvent<const bool> rampPausedChanged;
    //@ name=RampRate
    //@ oneway=true
    //@ optional=true
    Poco::BasicEvent<const Poco::UInt8> rampRateChanged;
    //@ name=StepDelta
    //@ oneway=true
    //@ optional=true
    Poco::BasicEvent<const Poco::UInt8> stepDeltaChanged;
};

Implementation Class

The implementation class is a subclass of the interface class and contains implementations for all pure virtual member functions defined in the interface class. The implementation class meant to be modified, as the member functions must be filled with code that performs the requested action.

Implementing Service Actions And Events

How the actual actions for a UPnP service are implemented is highly specific to the actual device or application. The UPnP framework does not make any restrictions in this regard. There are, however, some utility classes that simplify the implementation of evented state variables.

Examples for concrete service implementations can be found in the NetworkLight and SimpleMediaServer sample applications shipped with the source code.

Default Action Implementation

The default implementation for an action generated by UPnPGen looks as follows:

void DimmingImpl::getIsRamping(bool& retIsRamping)
{
    _logger.debug("Entering DimmingImpl::getIsRamping()");
    Poco::FastMutex::ScopedLock lock(_mutex);

    // TODO: Add implementation for action GetIsRamping here.
    throw Poco::NotImplementedException("Action: GetIsRamping");
}

First, a log message with the method name will be written to the class logger. Then, a mutex will be locked for the duration of the method call. It is possible that multiple actions are invoked simultaneously. In case these actions access the same resources, some serialization may be necessary. If the method implementation does not access shared resources, or protects access to shared resources by other means, locking the mutex can be removed. Finally, a Poco::NotImplementedException is thrown, as there is no meaningful implementation of the action yet. Of course, this line must be removed once the action has been implemented.

Evented State Variables

Implementing evented state variables properly can be hard, due to the event notification mechanism specified by UPnP. For example, if a control point registers itself for event notification, it immediately receives a notification containing the names and values of all available event variables. Furthermore, some UPnP services define moderation policies for evented state variables. This means that event notifications are not generated for every single change to the underlying state variable, but only if certain conditions are met. For example, event notifications may be rate limited, or may only be sent if the state variable's value changes for a certain minimum amount.

The GENA library provides some helper classes for implementing this behavior. For example, making the service implementation class a subclass of Poco::UPnP::GENA::StateProvider, implementing the currentState() member function and registering the instance with the GENA listener (by calling registerStateProvider()) ensures that all event subscribers receive the current values of all evented state variables when they subscribe.

For implementing moderated evented state variables, the Poco::UPnP::GENA::EventedStateVariable class template can be used. The class template is instantiated with the state variable type and a moderation policy class and ensures that change events are only fired under control of the moderation policy. Three policies are supported:

Example code for working with evented state variables can be found in the NetworkLight sample (class DimmingImpl).

Generating RemotingNG Infrastructure Code

The UPnP service class generated by the UPnP C++ code generator (UPnPGen) must be processed by the RemotingNG C++ code generator (RemoteGenNG) in order to generate the necessary infrastructure code (proxy, skeleton, helpers, etc.) for Remoting. For detailed information on how to invoke RemoteGenNG, and what RemoteGenNG does in detail, please refer to the Remoting documentation (Remoting Overview, Remoting Tutorial Part 1 and Remoting User Guide).

Invoking The RemotingNG Code Generator (RemoteGenNG)

In any case, a configuration file for RemoteGenNG must be created. The following example shows the configuration file used for the LightingControls_1 library that's shipped as part of the UPnP sample code.

<AppConfig>
    <RemoteGen>
        <files>
            <include>
                ${POCO_BASE}/RemotingNG/include/Poco/RemotingNG/RemoteObject.h
                ${POCO_BASE}/RemotingNG/include/Poco/RemotingNG/Proxy.h
                ${POCO_BASE}/RemotingNG/include/Poco/RemotingNG/Skeleton.h
                ${POCO_BASE}/RemotingNG/include/Poco/RemotingNG/EventDispatcher.h
                ${POCO_BASE}/RemotingNG/include/Poco/RemotingNG/EventSubscriber.h
                include/UPnPS/LightingControls1/Dimming.h
                include/UPnPS/LightingControls1/SwitchPower.h
            </include>
        </files>
        <output>
            <namespace>UPnPS::LightingControls1</namespace>
            <include>include/UPnPS/LightingControls1</include>
            <src>src</src>
            <includeRoot>include</includeRoot>
            <flatIncludes>false</flatIncludes>
            <library>LightingControls1</library>
            <alwaysInclude>UPnPS/LightingControls1/LightingControls1.h</alwaysInclude>
            <copyright>Copyright (c) 2011, Applied Informatics Software Engineering GmbH.
                All rights reserved.

                This is unpublished proprietary source code of Applied Informatics.
                The contents of this file may not be disclosed to third parties, 
                copied or duplicated in any form, in whole or in part.
            </copyright>
        </output>
        <compiler id="gcc">
            <exec>g++</exec>
            <options>
                -I${POCO_BASE}/Foundation/include
                -I${POCO_BASE}/RemotingNG/include
                -I${POCO_BASE}/UPnP/include
                -I./include
                -E
                -C
                -o%.i
            </options>
        </compiler>
        <compiler id="clang">
            <exec>clang++</exec>
            <options>
                -I${POCO_BASE}/Foundation/include
                -I${POCO_BASE}/RemotingNG/include
                -I${POCO_BASE}/UPnP/include
                -I./include
                -E
                -C
                -xc++
                -o%.i
            </options>
        </compiler>
        <compiler id="msvc">
            <exec>cl</exec>
            <options>
                /I "${POCO_BASE}\Foundation\include"
                /I "${POCO_BASE}\RemotingNG\include"
                /I "${POCO_BASE}\UPnP\include"
                /I ".\include"
                /nologo
                /C
                /P
                /TP
            </options>
        </compiler>
    </RemoteGen>
</AppConfig>

When invoking RemoteGenNG with this configuration file, the configuration variable POCO_BASE must be specified on the command-line. Following is an Example for invoking RemoteGenNG on Mac OS X or Linux. It is assumed that the environment variable POCO_BASE is set as required by the POCO build system.

RemoteGenNG -DPOCO_BASE=$POCO_BASE --mode=server --compiler=gcc RemoteGen.xml

Note that if you want to implement a UPnP control point, RemoteGenNG must also generate the client infrastructure files (proxy, etc.). In this case, invoke RemoteGen with —mode=client or —mode=both.

Project And Source Code Organization

We recommend putting the C++ classes generated by UPnPGen (service interface class) and RemoteGenNG (proxy, skeleton, helpers, etc.) into a separate library. This way, the classes can be easily reused by different projects if necessary. The actual implementation of the UPnP service should not be part of the library. Examples for such libraries can be found in the UPnP sample code (LightingControls_1 and MediaServer_1.

Please note that neither UPnPGen nor RemoteGenNG generate Makefiles or project files for Visual Studio. These must be created manually. The Makefiles and project files shipped with the sample code can be used as a starting point.

Setting Up The UPnP Framework In The Application

In order to set up the UPnP framework so that UPnP services can be provided, the following steps must be taken.

  1. Perform basic application-specific setup.
  2. Find a suitable network interface for use by the SSDP listener and the HTTP server.
  3. Set up the HTTP server.
  4. Set up the SOAP and GENA protocol infrastructure.
  5. Create and register the service implementation classes with the UPnP framework.
  6. Set up eventing.
  7. Set up SSDP and publish the device and services on the network.

These steps will be explained in the following.

Application-Specific Setup

Perform all the necessary setup required by your application. This also includes creating and initializing your service implementation classes. Also, various classes in the UPnP framework need a Poco::Util::Timer instance, so this is one of the first things to create in an application:

Poco::Util::Timer timer;

Finding A Network Interface

A suitable multicast-capable network interface must be found for use by the SSDP responder and the web server. Depending on your application or device, the network interface can be specified in the application configuration file, or it can be automatically detected.

Following code first looks in the application configuration file for a network interface name and uses that if one is provided, or, if none is provided, automatically detects a suitable network interface.

std::string mcastInterfaceName = config().getString("upnp.interface", "");
Poco::Net::NetworkInterface mcastInterface(
    mcastInterfaceName.empty() 
        ? findActiveNetworkInterface() 
        : Poco::Net::NetworkInterface::forName(mcastInterfaceName, Poco::Net::NetworkInterface::IPv4_ONLY));
Poco::Net::IPAddress ipAddress(mcastInterface.address());

The findActiveNetworkInterface() method is shown below.

Poco::Net::NetworkInterface findActiveNetworkInterface()
{
    Poco::Net::NetworkInterface::NetworkInterfaceList ifs = Poco::Net::NetworkInterface::list();
    for (Poco::Net::NetworkInterface::NetworkInterfaceList::iterator it = ifs.begin(); it != ifs.end(); ++it)
    {
        if (!it->address().isWildcard() && !it->address().isLoopback() && it->supportsIPv4()) return *it;
    }
    throw Poco::IOException("No configured Ethernet interface found");
}

Setting Up The HTTP Server

Setting up the HTTP server involves obtaining a port number from the application configuration (or by some other means), creating and initializing a Poco::Net::HTTPServerParams object for the Poco::Net::HTTPServer, and writing a Poco::Net::HTTPServerRequestHandler class. The request handler class must handle all incoming requests to the internal web server. These include requests for the device's web pages, requests for UPnP device and service descriptions, as well as SOAP and GENA requests for device control and eventing.

The request handler factory should also perform some basic sanity checks on incoming requests, so that invalid requests will be properly rejected. Examples for properly written request handler factory classes can be found in the NetworkLight and SimpleMediaServer sample applications.

The following conventions must be followed in order for SOAP and GENA requests to work:

The locations (paths) for web pages, device and service descriptions can be freely chosen. However, for consistency, we recommend to put the (root) device description under "/upnp/rootdevice.xml" and the service descriptions under "/upnp/<service>.xml".

For implementing the request handler for the device description page, we recommend using the PageCompiler tool.

An example page for the root device description is shown below.

<%@ page class="RootDeviceDescription"
         contentType="text/xml"
         contentLanguage="en"
         chunked="false"
         buffered="true"
%><%@ impl include="Poco/Util/Application.h"
%><%
Poco::Util::Application& app = Poco::Util::Application::instance();
%><?xml version="1.0"?>
<root xmlns="urn:schemas-upnp-org:device-1-0">
  <specVersion>
    <major>1</major>
    <minor>0</minor>
  </specVersion>
  <URLBase>http://<%= request.serverAddress().toString() %></URLBase>
  <device>
    <deviceType>urn:schemas-upnp-org:device:DimmableLight:1</deviceType>
    <friendlyName><%= app.config().getString("upnp.device.friendlyName") %></friendlyName>
    <manufacturer><%= app.config().getString("upnp.device.manufacturer") %></manufacturer>
    <manufacturerURL><%= app.config().getString("upnp.device.manufacturerURL") %></manufacturerURL>
    <modelDescription><%= app.config().getString("upnp.device.modelDescription") %></modelDescription>
    <modelName><%= app.config().getString("upnp.device.modelName") %></modelName>
    <UDN>uuid:<%= app.config().getString("upnp.device.uuid") %></UDN>
    <serviceList>
      <service>
        <serviceType>urn:schemas-upnp-org:service:SwitchPower:1</serviceType>
        <serviceId>urn:upnp-org:serviceId:SwitchPower.1</serviceId>
        <SCPDURL>/upnp/SwitchPower1.xml</SCPDURL>
        <controlURL>/upnp-control/SwitchPower/SwitchPower.1</controlURL>
        <eventSubURL>/upnp-eventing/SwitchPower/SwitchPower.1</eventSubURL>
      </service>
      <service>
        <serviceType>urn:schemas-upnp-org:service:Dimming:1</serviceType>
        <serviceId>urn:upnp-org:serviceId:Dimming.1</serviceId>
        <SCPDURL>/upnp/Dimming1.xml</SCPDURL>
        <controlURL>/upnp-control/Dimming/Dimming.1</controlURL>
        <eventSubURL>/upnp-eventing/Dimming/Dimming.1</eventSubURL>
      </service>
    </serviceList>
    <presentationURL>/index.html</presentationURL>
  </device>
</root>

Notable is the call to request.serverAddress().toString() to obtain the IP address of the device that must be given in the URLBase element. Also, the content of some descriptive elements (friendlyName, manufacturer, etc.), as well as the UUID of the device for the UDN element, are read from the application configuration.

The paths for controlURL and eventSubURL follow Remoting conventions and start with "/upnp-control/" (or "/upnp-eventing", respectively), followed by class name and object identifier. This is mandatory.

The service description documents contain only static information and can either be served from the filesystem, or they can be compiled into request handler classes with the help of the File2Page and PageCompiler utilities.

There is an interdependency between HTTP server and SOAP/GENA setup, so before creating the Poco::Net::HTTPServer instance and passing it the request handler factory object, the SOAP and GENA listener objects must be created, as they are needed by the request handler factory.

Setting Up SOAP And GENA

Setting up SOAP and GENA is straightforward. The GENA transport factory object must be registered with the RemotingNG ORB, so that event messages can be sent to subscribers.

Poco::UPnP::GENA::TransportFactory::registerFactory();

Note: There is a second variant of the registerFactory() method that allows to specify a HTTP timeout and enabling/disabling HTTP/1.1 persistent connections.

Then, the listener objects for SOAP and GENA must be created. Both need the socket address of the HTTP server, and the GENA listener also needs a Poco::Util::Timer object.

Poco::Net::SocketAddress httpSA(ipAddress, httpPort);   
Poco::UPnP::SOAP::Listener::Ptr pSOAPListener = new Poco::UPnP::SOAP::Listener(httpSA.toString());
Poco::UPnP::GENA::Listener::Ptr pGENAListener = new Poco::UPnP::GENA::Listener(httpSA.toString(), timer);

With the SOAP and GENA listeners created, we can now create the request handler factory and HTTP server objects, and the HTTP server can be started.

Poco::Net::ServerSocket httpSocket(httpPort);
Poco::Net::HTTPServer httpServer(new RequestHandlerFactory(*pSOAPListener, *pGENAListener), httpSocket, pHTTPParams);
httpServer.start();

Finally, the SOAP and GENA listeners must be registered with the RemotingNG ORB.

std::string soapListenerId = Poco::RemotingNG::ORB::instance().registerListener(pSOAPListener);
std::string genaListenerId = Poco::RemotingNG::ORB::instance().registerListener(pGENAListener);

Creating And Registering Service Implementation Classes

For every service, the service implementation class must be created, and the class must be registered with the RemotingNG ORB, so that its methods can be invoked by control points. Registering a service object with the ORB is done with the server helper class that has been automatically generated by RemoteGenNG. Note that service objects that provide events must be registered with both the SOAP and GENA transports.

Poco::SharedPtr<DimmingImpl> pDimmingImpl = new DimmingImpl;
UPnPS::LightingControls1::DimmingServerHelper::registerObject(pDimmingImpl, "Dimming.1", soapListenerId);
std::string dimmableURI = UPnPS::LightingControls1::DimmingServerHelper::registerObject(pDimmingImpl, "Dimming.1", genaListenerId);

Setting Up Eventing

To enable GENA events, the ORB must be configured to support events for the service object. This is done by calling the enableEvent() method of the server helper class. If the service class implements the Poco::UPnP::GENA::StateProvider interface, as is recommended for all services supporting events, the service object must be registered with the GENA listener as a state provider.

UPnPS::LightingControls1::DimmingServerHelper::enableEvents(dimmableURI, pGENAListener->protocol());
pGENAListener->registerStateProvider(dimmableURI, pDimmingImpl.get());

Setting Up SSDP

The final step is to set up the service discovery mechanism, so that the device and its services can be discovered on the network. Specifically, an advertisement must be created for the root device, the device, the device type and each supported service type.

Poco::Net::SocketAddress ssdpSA(Poco::Net::IPAddress(), SSDPResponder::MULTICAST_PORT);
Poco::Net::MulticastSocket ssdpSocket(ssdpSA);
ssdpSocket.setLoopback(true);
ssdpSocket.setTimeToLive(4);
ssdpSocket.setInterface(mcastInterface);
SSDPResponder responder(timer, ssdpSocket);
std::string deviceLocation = "http://" + httpSA.toString() + "/upnp/rootdevice.xml";
Poco::UUID deviceUUID(config().getString("upnp.device.uuid"));
int adLifetime = config().getInt("upnp.adLifetime", 60);
responder.publish(new Advertisement(Advertisement::AD_ROOT_DEVICE, deviceUUID, deviceLocation, adLifetime));
responder.publish(new Advertisement(Advertisement::AD_DEVICE, deviceUUID, deviceLocation, adLifetime));
responder.publish(new Advertisement(deviceUUID, Poco::UPnP::URN("urn:schemas-upnp-org:device:DimmableLight:1"), deviceLocation, adLifetime));
responder.publish(new Advertisement(deviceUUID, Poco::UPnP::URN("urn:schemas-upnp-org:service:SwitchPower:1"), deviceLocation, adLifetime));
responder.publish(new Advertisement(deviceUUID, Poco::UPnP::URN("urn:schemas-upnp-org:service:Dimming:1"), deviceLocation, adLifetime));
ssdpSocket.joinGroup(responder.groupAddress().host(), mcastInterface);
responder.start();

Once this has been done, the device is ready to be discovered on the network and to be controlled by a control point.

For more information about implementing UPnP devices, please refer to the NetworkLight and SimpleMediaServer sample applications shipped with the source code.

Securely control IoT edge devices from anywhere   Connect a Device