Blob Blame History Raw
<?xml version="1.0"?>
<!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN" "http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd">

<chapter id="client-tutorial">
  <title>Writing a UPnP Client</title>

  <simplesect>
    <title>Introduction</title>
    <para>
      This chapter explains how to write an application which fetches the
      external IP address from an UPnP-compliant modem.  To do this a
      <glossterm>Control Point</glossterm> is created, which searches for
      services of the type
      <literal>urn:schemas-upnp-org:service:WANIPConnection:1</literal> (part of
      the <ulink url="http://upnp.org/standardizeddcps/igd.asp">Internet Gateway
      Device</ulink> specification).  As services are discovered
      <firstterm>Service Proxy</firstterm> objects are created by GUPnP to allow
      interaction with the service, on which we can invoke the action
      <function>GetExternalIPAddress</function> to fetch the external IP
      address.
    </para>
  </simplesect>

  <simplesect>
    <title>Finding Services</title>
    <para>
      First, we initialize GUPnP and create a control point targeting the
      service type.  Then we connect a signal handler so that we are notified
      when services we are interested in are found.
    </para>
    <programlisting>#include &lt;libgupnp/gupnp-control-point.h&gt;

static GMainLoop *main_loop;

static void
service_proxy_available_cb (GUPnPControlPoint *cp,
                            GUPnPServiceProxy *proxy,
                            gpointer           userdata)
{
  /* &hellip; */
}

int
main (int argc, char **argv)
{
  GUPnPContext *context;
  GUPnPControlPoint *cp;
  
  /* Required initialisation */
  #if !GLIB_CHECK_VERSION(2,35,0)
    g_type_init ();
  #endif

  /* Create a new GUPnP Context.  By here we are using the default GLib main
     context, and connecting to the current machine's default IP on an
     automatically generated port. */
  context = gupnp_context_new (NULL, NULL, 0, NULL);

  /* Create a Control Point targeting WAN IP Connection services */
  cp = gupnp_control_point_new
    (context, "urn:schemas-upnp-org:service:WANIPConnection:1");

  /* The service-proxy-available signal is emitted when any services which match
     our target are found, so connect to it */
  g_signal_connect (cp,
		    "service-proxy-available",
		    G_CALLBACK (service_proxy_available_cb),
		    NULL);

  /* Tell the Control Point to start searching */
  gssdp_resource_browser_set_active (GSSDP_RESOURCE_BROWSER (cp), TRUE);
  
  /* Enter the main loop. This will start the search and result in callbacks to
     service_proxy_available_cb. */
  main_loop = g_main_loop_new (NULL, FALSE);
  g_main_loop_run (main_loop);

  /* Clean up */
  g_main_loop_unref (main_loop);
  g_object_unref (cp);
  g_object_unref (context);
  
  return 0;
}</programlisting>
  </simplesect>

  <simplesect>
    <title>Invoking Actions</title>
    <para>
      Now we have an application which searches for the service we specified and
      calls <function>service_proxy_available_cb</function> for each one it
      found.  To get the external IP address we need to invoke the
      <literal>GetExternalIPAddress</literal> action.  This action takes no in
      arguments, and has a single out argument called "NewExternalIPAddress".
      GUPnP has a set of methods to invoke actions (which will be very familiar
      to anyone who has used <literal>dbus-glib</literal>) where you pass a
      <constant>NULL</constant>-terminated varargs list of (name, GType, value)
      tuples for the in arguments, then a <constant>NULL</constant>-terminated
      varargs list of (name, GType, return location) tuples for the out
      arguments.
    </para>
    <programlisting>static void
service_proxy_available_cb (GUPnPControlPoint *cp,
                            GUPnPServiceProxy *proxy,
                            gpointer           userdata)
{
  GError *error = NULL;
  char *ip = NULL;
  
  gupnp_service_proxy_send_action (proxy,
				   /* Action name and error location */
				   "GetExternalIPAddress", &amp;error,
				   /* IN args */
				   NULL,
				   /* OUT args */
				   "NewExternalIPAddress",
				   G_TYPE_STRING, &amp;ip,
				   NULL);
  
  if (error == NULL) {
    g_print ("External IP address is %s\n", ip);
    g_free (ip);
  } else {
    g_printerr ("Error: %s\n", error-&gt;message);
    g_error_free (error);
  }
  g_main_loop_quit (main_loop);
}</programlisting>
    <para>
      Note that gupnp_service_proxy_send_action() blocks until the service has
      replied.  If you need to make non-blocking calls then use
      gupnp_service_proxy_begin_action(), which takes a callback that will be
      called from the mainloop when the reply is received.
    </para>
  </simplesect>

  <simplesect>
    <title>Subscribing to state variable change notifications</title>
    <para>
      It is possible to get change notifications for the service state variables 
      that have attribute <literal>sendEvents="yes"</literal>. We'll demonstrate
      this by modifying <function>service_proxy_available_cb</function> and using
      gupnp_service_proxy_add_notify() to setup a notification callback:
    </para>
    <programlisting>static void
external_ip_address_changed (GUPnPServiceProxy *proxy,
                             const char        *variable,
                             GValue            *value,
                             gpointer           userdata)
{
  g_print ("External IP address changed: %s\n", g_value_get_string (value));
}

static void
service_proxy_available_cb (GUPnPControlPoint *cp,
                            GUPnPServiceProxy *proxy,
                            gpointer           userdata)
{
  g_print ("Found a WAN IP Connection service\n");
  
  gupnp_service_proxy_set_subscribed (proxy, TRUE);
  if (!gupnp_service_proxy_add_notify (proxy,
                                       "ExternalIPAddress",
                                       G_TYPE_STRING,
                                       external_ip_address_changed,
                                       NULL)) {
    g_printerr ("Failed to add notify");
  }
}</programlisting>
  </simplesect>

  <simplesect>
    <title>Generating Wrappers</title>
    <para>
      Using gupnp_service_proxy_send_action() and gupnp_service_proxy_add_notify ()
      can become tedious, because of the requirement to specify the types and deal
      with GValues.  An
      alternative is to use <xref linkend="gupnp-binding-tool"/>, which
      generates wrappers that hide the boilerplate code from you.  Using a 
      wrapper generated with prefix 'ipconn' would replace 
      gupnp_service_proxy_send_action() with this code:
    </para>
    <programlisting>ipconn_get_external_ip_address (proxy, &amp;ip, &amp;error);</programlisting>
    <para>
      State variable change notifications are friendlier with wrappers as well:
    </para>
    <programlisting>static void
external_ip_address_changed (GUPnPServiceProxy *proxy,
                             const gchar       *external_ip_address,
                             gpointer           userdata)
{
  g_print ("External IP address changed: '%s'\n", external_ip_address);
}

static void
service_proxy_available_cb (GUPnPControlPoint *cp,
                            GUPnPServiceProxy *proxy
                            gpointer           userdata)
{
  g_print ("Found a WAN IP Connection service\n");
  
  gupnp_service_proxy_set_subscribed (proxy, TRUE);
  if (!ipconn_external_ip_address_add_notify (proxy,
                                              external_ip_address_changed,
                                              NULL)) {
    g_printerr ("Failed to add notify");
  }
}</programlisting>
  </simplesect>
</chapter>