1<?xml version="1.0"?> 2<!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN" "http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd"> 3 4<chapter id="server-tutorial" xmlns:xi="http://www.w3.org/2001/XInclude"> 5 <title>Writing a UPnP Service</title> 6 7 <simplesect> 8 <title>Introduction</title> 9 <para> 10 This chapter explains how to implement a UPnP service using GUPnP. For 11 this example we will create a virtual UPnP-enabled light bulb. 12 </para> 13 <para> 14 Before any code can be written, the device and services that it implement 15 need to be described in XML. Although this can be frustrating, if you are 16 implementing a standardised service (see <ulink 17 url="http://upnp.org/sdcps-and-certification/standards/sdcps/"/> for the 18 list of standard devices and services) then the service description is 19 already written for you and the device description is trivial. UPnP has 20 standardised <ulink url="http://upnp.org/specs/ha/lighting/">Lighting 21 Controls</ulink>, so we'll be using the device and service types defined 22 there. 23 </para> 24 </simplesect> 25 26 <simplesect> 27 <title>Defining the Device</title> 28 <para> 29 The first step is to write the <firstterm>device description</firstterm> 30 file. This is a short XML document which describes the device and what 31 services it provides (for more details see the <ulink 32 url="http://upnp.org/specs/arch/UPnP-arch-DeviceArchitecture-v1.0.pdf">UPnP 33 Device Architecture</ulink> specification, section 2.1). We'll be using 34 the <literal>BinaryLight1</literal> device type, but if none of the 35 existing device types are suitable a custom device type can be created. 36 </para> 37 <programlisting><xi:include href="../examples/BinaryLight1.xml" parse="text"><xi:fallback /></xi:include></programlisting> 38 <para> 39 The <sgmltag>specVersion</sgmltag> tag defines what version of the UPnP 40 Device Architecture the document conforms to. At the time of writing the 41 only version is 1.0. 42 </para> 43 <para> 44 Next there is the root <sgmltag>device</sgmltag> tag. This contains 45 metadata about the device, lists the services it provides and any 46 sub-devices present (there are none in this example). The 47 <sgmltag>deviceType</sgmltag> tag specifies the type of the device. 48 </para> 49 <para> 50 Next we have <sgmltag>friendlyName</sgmltag>, 51 <sgmltag>manufacturer</sgmltag> and <sgmltag>modelName</sgmltag>. The 52 friendly name is a human-readable name for the device, the manufacturer 53 and model name are self-explanatory. 54 </para> 55 <para> 56 Next there is the UDN, or <firstterm>Unique Device Name</firstterm>. This 57 is an identifier which is unique for each device but persistent for each 58 particular device. Although it has to start with <literal>uuid:</literal> 59 note that it doesn't have to be an UUID. There are several alternatives 60 here: for example it could be computed at built-time if the software will 61 only be used on a single machine, or it could be calculated using the 62 device's serial number or MAC address. 63 </para> 64 <para> 65 Finally we have the <sgmltag>serviceList</sgmltag> which describes the 66 services this device provides. Each service has a service type (again 67 there are types defined for standardised services or you can create your 68 own), service identifier, and three URLs. As a service type we're using 69 the standard <literal>SwitchPower1</literal> service. The 70 <sgmltag>SCPDURL</sgmltag> field specifies where the <firstterm>Service 71 Control Protocol Document</firstterm> can be found, this describes the 72 service in more detail and will be covered next. Finally there are the 73 control and event URLs, which need to be unique on the device and will be 74 managed by GUPnP. 75 </para> 76 </simplesect> 77 78 <simplesect> 79 <title>Defining Services</title> 80 <para> 81 Because we are using a standard service we can use the service description 82 from the specification. This is the <literal>SwitchPower1</literal> 83 service description file: 84 </para> 85 <programlisting><xi:include href="../examples/SwitchPower1.xml" parse="text"><xi:fallback /></xi:include></programlisting> 86 <para> 87 Again, the <sgmltag>specVersion</sgmltag> tag defines the UPnP version 88 that is being used. The rest of the document consists of an 89 <sgmltag>actionList</sgmltag> defining the <glossterm 90 linkend="action">actions</glossterm> available and a 91 <sgmltag>serviceStateTable</sgmltag> defining the <glossterm 92 linkend="state-variable">state variables</glossterm>. 93 </para> 94 <para> 95 Every <sgmltag>action</sgmltag> has a <sgmltag>name</sgmltag> and a list 96 of <sgmltag>argument</sgmltag>s. Arguments also have a name, a direction 97 (<literal>in</literal> or <literal>out</literal> for input or output 98 arguments) and a related state variable. The state variable is used to 99 determine the type of the argument, and as such is a required element. 100 This can lead to the creation of otherwise unused state variables to 101 define the type for an argument (the <literal>WANIPConnection</literal> 102 service is a good example of this), thanks to the legacy behind UPnP. 103 </para> 104 <para> 105 <sgmltag>stateVariable</sgmltag>s need to specify their 106 <sgmltag>name</sgmltag> and <sgmltag>dataType</sgmltag>. State variables 107 by default send notifications when they change, to specify that a variable 108 doesn't do this set the <sgmltag>sendEvents</sgmltag> attribute to 109 <literal>no</literal>. Finally there are optional 110 <sgmltag>defaultValue</sgmltag>, <sgmltag>allowedValueList</sgmltag> and 111 <sgmltag>allowedValueRange</sgmltag> elements which specify what the 112 default and valid values for the variable. 113 </para> 114 <para> 115 For the full specification of the service definition file, including a 116 complete list of valid <sgmltag>dataType</sgmltag>s, see section 2.3 of 117 the <ulink 118 url="http://upnp.org/specs/arch/UPnP-arch-DeviceArchitecture-v1.0.pdf">UPnP 119 Device Architecture</ulink>. 120 </para> 121 </simplesect> 122 123 <simplesect> 124 <title>Implementing the Device</title> 125 <para> 126 Before starting to implement the device, some boilerplate code is needed 127 to initialise GUPnP. GLib types and threading needs to be initialised, 128 and then a GUPnP context can be created using gupnp_context_new(). 129 </para> 130 <programlisting>GUPnPContext *context; 131/* Initialize required subsystems */ 132#if !GLIB_CHECK_VERSION(2,35,0) 133 g_type_init (); 134#endif 135/* Create the GUPnP context with default host and port */ 136context = gupnp_context_new (NULL, NULL, 0, NULL);</programlisting> 137 <para> 138 Next the root device can be created. The name of the device description 139 file can be passed as an absolute file path or a relative path to the 140 second parameter of gupnp_root_device_new(). The service description 141 files referenced in the device description are expected to be at the path 142 given there as well. 143 </para> 144 <programlisting>GUPnPRootDevice *dev; 145/* Create the root device object */ 146dev = gupnp_root_device_new (context, "BinaryLight1.xml", "."); 147/* Activate the root device, so that it announces itself */ 148gupnp_root_device_set_available (dev, TRUE);</programlisting> 149 <para> 150 GUPnP scans the device description and any service description files it 151 refers to, so if the main loop was entered now the device and service 152 would be available on the network, albeit with no functionality. The 153 remaining task is to implement the services. 154 </para> 155 </simplesect> 156 157 <simplesect> 158 <title>Implementing a Service</title> 159 <para> 160 To implement a service we first fetch the #GUPnPService from the root 161 device using gupnp_device_info_get_service() (#GUPnPRootDevice is a 162 subclass of #GUPnPDevice, which implements #GUPnPDeviceInfo). This 163 returns a #GUPnPServiceInfo which again is an interface, implemented by 164 #GUPnPService (on the server) and #GUPnPServiceProxy (on the client). 165 </para> 166 <programlisting>GUPnPServiceInfo *service; 167service = gupnp_device_info_get_service 168 (GUPNP_DEVICE_INFO (dev), "urn:schemas-upnp-org:service:SwitchPower:1");</programlisting> 169 <para> 170 #GUPnPService handles interacting with the network itself, leaving the 171 implementation of the service itself to signal handlers that we need to 172 connect. There are two signals: #GUPnPService::action-invoked and 173 #GUPnPService::query-variable. #GUPnPService::action-invoked is emitted 174 when a client invokes an action: the handler is passed a 175 #GUPnPServiceAction object that identifies which action was invoked, and 176 is used to return values using gupnp_service_action_set(). 177 #GUPnPService::query-variable is emitted for evented variables when a 178 control point subscribes to the service (to announce the initial value), 179 or whenever a client queries the value of a state variable (note that this 180 is now deprecated behaviour for UPnP control points): the handler is 181 passed the variable name and a #GValue which should be set to the current 182 value of the variable. 183 </para> 184 <para> 185 Handlers should be targetted at specific actions or variables by using 186 the <firstterm>signal detail</firstterm> when connecting. For example, 187 this causes <function>on_get_status_action</function> to be called when 188 the <function>GetStatus</function> action is invoked: 189 </para> 190 <programlisting>static void on_get_status_action (GUPnPService *service, GUPnPServiceAction *action, gpointer user_data); 191… 192g_signal_connect (service, "action-invoked::GetStatus", G_CALLBACK (on_get_status_action), NULL);</programlisting> 193 <para> 194 The implementation of action handlers is quite simple. The handler is 195 passed a #GUPnPServiceAction object which represents the in-progress 196 action. If required it can be queried using 197 gupnp_service_action_get_name() to identify the action (this isn't 198 required if detailed signals were connected). Any 199 <firstterm>in</firstterm> arguments can be retrieving using 200 gupnp_service_action_get(), and then return values can be set using 201 gupnp_service_action_set(). Once the action has been performed, either 202 gupnp_service_action_return() or gupnp_service_action_return_error() 203 should be called to either return successfully or return an error code. 204 If any evented state variables were modified during the action then a 205 notification should be emitted using gupnp_service_notify(). This is an 206 example implementation of <function>GetStatus</function> and 207 <function>SetTarget</function>: 208 </para> 209 <programlisting>static gboolean status; 210 211static void 212get_status_cb (GUPnPService *service, GUPnPServiceAction *action, gpointer user_data) 213{ 214 gupnp_service_action_set (action, 215 "ResultStatus", G_TYPE_BOOLEAN, status, 216 NULL); 217 gupnp_service_action_return (action); 218} 219 220void 221set_target_cb (GUPnPService *service, GUPnPServiceAction *action, gpointer user_data) 222{ 223 gupnp_service_action_get (action, 224 "NewTargetValue", G_TYPE_BOOLEAN, &status, 225 NULL); 226 gupnp_service_action_return (action); 227 gupnp_service_notify (service, "Status", G_TYPE_STRING, status, NULL); 228} 229… 230g_signal_connect (service, "action-invoked::GetStatus", G_CALLBACK (get_status_cb), NULL); 231g_signal_connect (service, "action-invoked::SetTarget", G_CALLBACK (set_target_cb), NULL);</programlisting> 232 <para> 233 State variable query handlers are called with the name of the variable and 234 a #GValue. This value should be initialized with the relevant type and 235 then set to the current value. Again signal detail can be used to connect 236 handlers to specific state variable callbacks. 237 </para> 238 <programlisting>static gboolean status; 239 240static void 241query_status_cb (GUPnPService *service, char *variable, GValue *value, gpointer user_data) 242{ 243 g_value_init (value, G_TYPE_BOOLEAN); 244 g_value_set_boolean (value, status); 245} 246… 247g_signal_connect (service, "query-variable::Status", G_CALLBACK (query_status_cb), NULL);</programlisting> 248 <para> 249 The service is now fully implemented. To complete it, enter a GLib main 250 loop and wait for a client to connect. The complete source code for this 251 example is available as <filename>examples/light-server.c</filename> in 252 the GUPnP sources. 253 </para> 254 <para> 255 For services which have many actions and variables there is a convenience 256 method gupnp_service_signals_autoconnect() which will automatically 257 connect specially named handlers to signals. See the documentation for 258 full details on how it works. 259 </para> 260 </simplesect> 261 <simplesect> 262 <title>Generating Service-specific Wrappers</title> 263 <para> 264 Using service-specific wrappers can simplify the implementation of a service. 265 Wrappers can be generated with <xref linkend="gupnp-binding-tool"/> 266 using the option <literal>--mode server</literal>. 267 </para> 268 <para> 269 In the following examples the wrapper has been created with 270 <literal>--mode server --prefix switch</literal>. Please note that the callback handlers 271 (<literal>get_status_cb</literal> and <literal>set_target_cb</literal>) are not automatically 272 generated by <xref linkend="gupnp-binding-tool"/> for you. 273 </para> 274 <programlisting>static gboolean status; 275 276static void 277get_status_cb (GUPnPService *service, 278 GUPnPServiceAction *action, 279 gpointer user_data) 280{ 281 switch_get_status_action_set (action, status); 282 283 gupnp_service_action_return (action); 284} 285 286static void 287set_target_cb (GUPnPService *service, 288 GUPnPServiceAction *action, 289 gpointer user_data) 290{ 291 switch_set_target_action_get (action, &status); 292 switch_status_variable_notify (service, status); 293 294 gupnp_service_action_return (action); 295} 296 297… 298 299switch_get_status_action_connect (service, G_CALLBACK(get_status_cb), NULL); 300switch_set_target_action_connect (service, G_CALLBACK(set_target_cb), NULL);</programlisting> 301 <para> 302 Note how many possible problem situations that were run-time errors are 303 actually compile-time errors when wrappers are used: Action names, 304 argument names and argument types are easier to get correct (and available 305 in editor autocompletion). 306 </para> 307 <para> 308 State variable query handlers are implemented in a similar manner, but 309 they are even simpler as the return value of the handler is the state 310 variable value. 311 </para> 312 <programlisting>static gboolean 313query_status_cb (GUPnPService *service, 314 gpointer user_data) 315{ 316 return status; 317} 318 319… 320 321switch_status_query_connect (service, query_status_cb, NULL);</programlisting> 322 </simplesect> 323</chapter> 324