Herqq
|
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.
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.
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:
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.
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.
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.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:
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.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.