[rygel] core: Add initial EnergyManagement service implementation



commit 0ca67df803f8f31070b88c613e5ce19565e973a8
Author: Jussi Kukkonen <jussi kukkonen intel com>
Date:   Mon Sep 2 11:28:28 2013 +0300

    core: Add initial EnergyManagement service implementation
    
    EnergyManagement is a way to tell controlpoints that the device
    (e.g. Mediaserver or Renderer) may suspend, and also advice on
    how it can be woken up:
    http://upnp.org/specs/lp/UPnP-lp-EnergyManagement-v1-Service.pdf

 data/xml/EnergyManagement.xml.in                   |  127 +++++++++
 data/xml/Makefile.am                               |    3 +-
 doc/man/rygel.conf.xml                             |   40 +++
 src/librygel-core/Makefile.am                      |    2 +
 src/librygel-core/filelist.am                      |    1 +
 .../rygel-energy-management-helper.vapi            |    9 +
 src/librygel-core/rygel-energy-management.vala     |  282 ++++++++++++++++++++
 src/librygel-core/rygel-plugin.vala                |   17 ++
 8 files changed, 480 insertions(+), 1 deletions(-)
---
diff --git a/data/xml/EnergyManagement.xml.in b/data/xml/EnergyManagement.xml.in
new file mode 100644
index 0000000..59a5da7
--- /dev/null
+++ b/data/xml/EnergyManagement.xml.in
@@ -0,0 +1,127 @@
+<?xml version="1.0"?>
+<scpd xmlns="urn:schemas-upnp-org:service-1-0">
+   <specVersion>
+      <major>1</major>
+      <minor>0</minor>
+   </specVersion>
+
+   <actionList>
+      <action>
+         <name>GetInterfaceInfo</name>
+         <argumentList>
+            <argument>
+               <name>NetworkInterfaceInfo</name>
+               <direction>out</direction>
+               <relatedStateVariable>NetworkInterfaceInfo</relatedStateVariable>
+            </argument>
+            <argument>
+               <name>ProxiedNetworkInterfaceInfo</name>
+               <direction>out</direction>
+               <relatedStateVariable>ProxiedNetworkInterfaceInfo</relatedStateVariable>
+            </argument>
+         </argumentList>
+      </action>
+
+<!--
+      <action>
+         <name>ServiceSubscription</name>
+         <argumentList>
+            <argument>
+               <name>UniqueServiceName</name>
+               <direction>in</direction>
+               <relatedStateVariable>A_ARG_TYPE_UniqueServiceName</relatedStateVariable>
+            </argument>
+            <argument>
+               <name>ResourceURI</name>
+               <direction>in</direction>
+               <relatedStateVariable>A_ARG_TYPE_URI</relatedStateVariable>
+            </argument>
+            <argument>
+               <name>DurationRequest</name>
+               <direction>in</direction>
+               <relatedStateVariable>A_ARG_TYPE_Duration</relatedStateVariable>
+            </argument>
+            <argument>
+               <name>ServiceSubscriptionID</name>
+               <direction>out</direction>
+               <relatedStateVariable>A_ARG_TYPE_ServiceSubscriptionID</relatedStateVariable>
+            </argument>
+            <argument>
+               <name>Duration</name>
+               <direction>out</direction>
+               <relatedStateVariable>A_ARG_TYPE_Duration</relatedStateVariable>
+            </argument>
+         </argumentList>
+      </action>
+
+      <action>
+         <name>ServiceRenewal</name>
+         <argumentList>
+            <argument>
+               <name>DurationRequest</name>
+               <direction>in</direction>
+               <relatedStateVariable>A_ARG_TYPE_Duration</relatedStateVariable>
+            </argument>
+            <argument>
+               <name>ServiceSubscriptionID</name>
+               <direction>in</direction>
+               <relatedStateVariable>A_ARG_TYPE_ServiceSubscriptionID</relatedStateVariable>
+            </argument>
+            <argument>
+               <name>Duration</name>
+               <direction>out</direction>
+               <relatedStateVariable>A_ARG_TYPE_Duration</relatedStateVariable>
+            </argument>
+         </argumentList>
+      </action>
+
+      <action>
+         <name>ServiceRelease</name>
+         <argumentList>
+            <argument>
+               <name>ServiceSubscriptionID</name>
+               <direction>in</direction>
+               <relatedStateVariable>A_ARG_TYPE_ServiceSubscriptionID</relatedStateVariable>
+            </argument>
+         </argumentList>
+      </action>
+-->
+
+   </actionList>
+
+   <serviceStateTable>
+      <stateVariable sendEvents="yes">
+         <name>NetworkInterfaceInfo</name>
+         <dataType>string</dataType>
+      </stateVariable>
+
+      <stateVariable sendEvents="no">
+         <name>ProxiedNetworkInterfaceInfo</name>
+         <dataType>string</dataType>
+      </stateVariable>
+
+<!--
+      <stateVariable sendEvents="no">
+         <name>A_ARG_TYPE_Duration</name>
+         <dataType>ui4</dataType>
+      </stateVariable>
+
+      <stateVariable sendEvents="no">
+         <name>A_ARG_TYPE_ServiceSubscriptionID</name>
+         <dataType>ui4</dataType>
+      </stateVariable>
+
+      <stateVariable sendEvents="no">
+         <name>A_ARG_TYPE_UniqueServiceName</name>
+         <dataType>string</dataType>
+      </stateVariable>
+
+      <stateVariable sendEvents="no">
+         <name>A_ARG_TYPE_URI</name>
+         <dataType>string</dataType>
+      </stateVariable>
+-->
+
+   </serviceStateTable>
+
+</scpd>
diff --git a/data/xml/Makefile.am b/data/xml/Makefile.am
index c0f9f29..5fe4f1f 100644
--- a/data/xml/Makefile.am
+++ b/data/xml/Makefile.am
@@ -1,4 +1,5 @@
-xml_in_files = MediaServer3.xml.in \
+xml_in_files = EnergyManagement.xml.in \
+               MediaServer3.xml.in \
                MediaRenderer2.xml.in \
                RuihServer2.xml.in \
                ContentDirectory.xml.in \
diff --git a/doc/man/rygel.conf.xml b/doc/man/rygel.conf.xml
index 5cab37f..057c8b0 100644
--- a/doc/man/rygel.conf.xml
+++ b/doc/man/rygel.conf.xml
@@ -276,6 +276,16 @@ man(1), man(7), http://www.tldp.org/HOWTO/Man-Page/
       </varlistentry>
       <varlistentry>
         <term>
+          <option>energy-management</option>
+        </term>
+        <listitem>
+          <para>
+            Set to <userinput>true</userinput> to if you would like the UPnP device to contain a 
EnergyManagement service. Not that additional configuration is required, see EnergyManagement settings.
+          </para>
+        </listitem>
+      </varlistentry>
+      <varlistentry>
+        <term>
           <option>diagnostics</option>
         </term>
         <listitem>
@@ -287,6 +297,36 @@ man(1), man(7), http://www.tldp.org/HOWTO/Man-Page/
     </variablelist>
   </refsect1>
   <refsect1>
+    <title>EnergyManagement settings</title>
+    <para>The settings in <option>[EnergyManagement-IFACENAME]</option> sections specify the settings that 
relate to EnergyManagement services on this interface. Example: 
<option>[EnergyManagement-eth0].</option></para>
+    <variablelist>
+      <varlistentry>
+        <term>
+          <option>mode-on-suspend</option>
+        </term>
+        <listitem>
+          <para>The <code>NetworkInterfaceMode</code> that should be used when suspended. Default is 
"Unimplemented", other valid values are "IP-up-Periodic”, "IP-down-no-Wake", "IP-down-with-WakeOn", 
"IP-down-with-WakeAuto", "IP-down-with-WakeOnAuto".</para>
+        </listitem>
+      </varlistentry>
+      <varlistentry>
+        <term>
+          <option>supported-transport</option>
+        </term>
+        <listitem>
+          <para>Optional <code>WakeSupportedTransport</code> that the service should advertize. Valid values 
are "UDP-Broadcast", "UDP-Unicast", "TCP-Unicast", "Other".</para>
+        </listitem>
+      </varlistentry>
+      <varlistentry>
+        <term>
+          <option>password</option>
+        </term>
+        <listitem>
+          <para>Optional hexadecimal password that will be used to build the 
<code>WakeOnPattern</code>.</para>
+        </listitem>
+      </varlistentry>
+    </variablelist>
+  </refsect1>
+  <refsect1>
     <title>Tracker Plugin</title>
     <para>The tracker plugin uses the centralized database of meta information
       from the tracker project. See the
diff --git a/src/librygel-core/Makefile.am b/src/librygel-core/Makefile.am
index 715be18..97f51c7 100644
--- a/src/librygel-core/Makefile.am
+++ b/src/librygel-core/Makefile.am
@@ -17,6 +17,8 @@ librygel_core_2_4_la_VALAFLAGS = \
        --vapidir=$(srcdir) \
        --pkg uuid \
        --pkg posix \
+       --pkg linux \
+       --pkg rygel-energy-management-helper \
        $(LIBRYGEL_CORE_DEPS_VALAFLAGS) \
        $(RYGEL_COMMON_VALAFLAGS)
 
diff --git a/src/librygel-core/filelist.am b/src/librygel-core/filelist.am
index 70c00ec..4ac48da 100644
--- a/src/librygel-core/filelist.am
+++ b/src/librygel-core/filelist.am
@@ -7,6 +7,7 @@ LIBRYGEL_CORE_VAPI_SOURCE_FILES = \
        rygel-basic-management-test-traceroute.vala \
        rygel-description-file.vala \
        rygel-dlna-profile.vala \
+       rygel-energy-management.vala \
        rygel-root-device.vala \
        rygel-root-device-factory.vala \
        rygel-dbus-interface.vala \
diff --git a/src/librygel-core/rygel-energy-management-helper.vapi 
b/src/librygel-core/rygel-energy-management-helper.vapi
new file mode 100644
index 0000000..bdb66bf
--- /dev/null
+++ b/src/librygel-core/rygel-energy-management-helper.vapi
@@ -0,0 +1,9 @@
+namespace Rygel.EnergyManagementHelper {
+    /* Workaround for https://bugzilla.gnome.org/show_bug.cgi?id=707180 */
+    [CCode (cname = "struct sockaddr", cheader_filename = "sys/socket.h", destroy_function = "")]
+    public struct SockAddr {
+        public int sa_family;
+        [CCode (array_length = false)]
+        public char[] sa_data;
+    }
+}
diff --git a/src/librygel-core/rygel-energy-management.vala b/src/librygel-core/rygel-energy-management.vala
new file mode 100644
index 0000000..ab8e113
--- /dev/null
+++ b/src/librygel-core/rygel-energy-management.vala
@@ -0,0 +1,282 @@
+/*
+ * Copyright (C) 2013 Intel Corporation.
+ *
+ * Author: Jussi Kukkonen <jussi kukkonen intel com>
+ *
+ * This file is part of Rygel.
+ *
+ * Rygel is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * Rygel is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+/* EnergyManagement service implementation. The service will be run on
+ * plugins that have
+ *     energy-management=true
+ * set in their configuration. It requires UPower to function properly.
+ *
+ * Every network interface that supports Wake-On should have a
+ * configuration group:
+ *     [EnergyManagement-eth0]
+ *     mode-on-suspend=IP-down-WakeOn
+ *     supported-transport=UDP-Broadcast
+ *     password=FEEDDEADBEEF
+ * mode-on-suspend is required (without it the mode will always be
+ * "Unimplemented"), other configuration items are not.
+ */
+
+
+using Gee;
+using GLib;
+using GUPnP;
+
+[DBus (name = "org.freedesktop.UPower")]
+interface UPower : Object {
+    public signal void sleeping ();
+    public signal void resuming ();
+}
+
+/**
+ * Implementation of UPnP EnergyManagement service.
+ */
+public class Rygel.EnergyManagement : Service {
+    public const string UPNP_ID = "urn:upnp-org:serviceId:EnergyManagement";
+    public const string UPNP_TYPE = "urn:schemas-upnp-org:service:EnergyManagement:1";
+    public const string DESCRIPTION_PATH = "xml/EnergyManagement.xml";
+
+    private const string TEMPLATE = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
+                                    "<NetworkInterfaceInfo 
xsi:schemaLocation=\"urn:schemas-upnp-org:lp:em-NetworkInterfaceInfo 
http://www.upnp.org/schemas/lp/em-NetworkInterfaceInfo.xsd\"; " +
+                                    "                      
xmlns=\"urn:schemas-upnp-org:lp:em-NetworkInterfaceInfo\" " +
+                                    "                      
xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\";>" +
+                                    "%s" +
+                                    "</NetworkInterfaceInfo>";
+
+    private Configuration config;
+    private bool sleeping;
+    private UPower upower;
+
+    public override void constructed () {
+        base.constructed ();
+
+        this.config = MetaConfig.get_default ();
+
+        this.sleeping = false;
+
+
+        try {
+            this.upower = Bus.get_proxy_sync
+                                        (BusType.SYSTEM,
+                                         "org.freedesktop.UPower",
+                                         "/org/freedesktop/UPower",
+                                         DBusProxyFlags.DO_NOT_LOAD_PROPERTIES);
+            this.upower.sleeping.connect (this.upower_sleeping_cb);
+            this.upower.resuming.connect (this.upower_resuming_cb);
+        } catch (GLib.IOError err) {
+            /* this will lead to NetworkInterfaceMode "Unimplemented" */
+        }
+
+        this.query_variable["NetworkInterfaceInfo"].connect
+                                        (this.query_network_interface_info_cb);
+        this.query_variable["ProxiedNetworkInterfaceInfo"].connect
+                                        (this.query_proxied_network_interface_info_cb);
+
+        this.action_invoked["GetInterfaceInfo"].connect
+                                        (this.get_interface_info_cb);
+    }
+
+    private void upower_sleeping_cb () {
+        if (this.sleeping == true) {
+            return;
+        }
+
+        this.sleeping = true;
+        this.notify ("NetworkInterfaceInfo",
+                     typeof (string),
+                     this.create_network_interface_info ());
+    }
+
+    private void upower_resuming_cb () {
+        if (this.sleeping == false) {
+            return;
+        }
+
+        this.sleeping = false;
+        this.notify ("NetworkInterfaceInfo",
+                     typeof (string),
+                     this.create_network_interface_info ());
+    }
+
+    private bool get_mac_and_network_type (string iface,
+                                           out string mac,
+                                           out string type) {
+        var success = true;
+
+        var sock = Posix.socket (Posix.AF_INET, Posix.SOCK_STREAM, 0);
+        if (sock == -1) {
+            warning ("Failed to get a socket: %s\n", strerror (errno));
+            mac = "00:00:00:00:00:00";
+            type = "Other";
+
+            return false;
+        }
+
+        var ifreq = Linux.Network.IfReq ();
+        var ifreqp = (Linux.Network.IfReq*)(&ifreq);
+        Posix.strncpy ((string)ifreqp->ifr_name,
+                       iface,
+                       Linux.Network.INTERFACE_NAME_SIZE);
+
+        if (Posix.ioctl (sock, Linux.Network.SIOCGIFHWADDR, &ifreq) < 0) {
+            warning ("Failed to get mac address: %s\n",
+                     strerror (errno));
+            mac = "00:00:00:00:00:00";
+            success = false;
+        } else {
+            /* workaround for https://bugzilla.gnome.org/show_bug.cgi?id=707180 */
+            EnergyManagementHelper.SockAddr *addr =
+                                        (EnergyManagementHelper.SockAddr*)(&ifreq.ifr_hwaddr);
+
+            mac = "%02X:%02X:%02X:%02X:%02X:%02X".printf
+                                        ((uchar)addr.sa_data[0], (uchar)addr.sa_data[1],
+                                         (uchar)addr.sa_data[2], (uchar)addr.sa_data[3],
+                                         (uchar)addr.sa_data[4], (uchar)addr.sa_data[5]);
+        }
+
+        /* Note that this call really takes a struct IwReq, but this
+         * works since we only test if the call fails or not */
+        var ret_val = Posix.ioctl (sock, Linux.WirelessExtensions.SIOCGIWNAME, &ifreq);
+        if (ret_val == -1) {
+            type = "Ethernet";
+        } else {
+            type = "Wi-Fi";
+        }
+
+        return success;
+    }
+
+    private string create_network_interface_info () {
+
+        string mac_address, type;
+        bool success = true;
+
+        var iface = this.root_device.context.interface;
+        var config_section ="EnergyManagement-%s".printf (iface);
+
+        success = this.get_mac_and_network_type (iface, out mac_address, out type);
+
+        var mac = mac_address.replace (":", "");
+
+        var wake_pattern = "FFFFFFFFFFFF%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s".printf (
+                                        mac, mac, mac, mac, mac, mac, mac, mac,
+                                        mac, mac, mac, mac, mac, mac, mac, mac);
+        try {
+            var password = this.config.get_string (config_section, "password");
+            wake_pattern = wake_pattern.concat (password);
+        } catch (GLib.Error error) { }
+
+        var ip_addr = new InetAddress.from_string (this.root_device.context.host_ip);
+        bool is_ipv6 = (ip_addr != null && ip_addr.family == SocketFamily.IPV6);
+        var associated_ips = "<Ipv%d>%s</Ipv%d>".printf
+                                        (is_ipv6 ? 6 : 4,
+                                         this.root_device.context.host_ip,
+                                         is_ipv6 ? 6 : 4);
+
+        string mode;
+        if (!success || this.upower == null) {
+            mode = "Unimplemented";
+        } else {
+            try {
+                /* Note: we want to check the value exists even when not sleeping */
+                var sleep_mode = this.config.get_string (config_section,
+                                                         "mode-on-suspend");
+                mode = this.sleeping ? sleep_mode : "IP-up";
+            } catch (GLib.Error error) {
+                mode = "Unimplemented";
+            }
+        }
+
+        string transport_node;
+        try {
+            var val = this.config.get_string (config_section,
+                                              "supported-transport");
+            transport_node = "<WakeSupportedTransport>%s</WakeSupportedTransport>".printf
+                                        (val);
+        } catch (GLib.Error error) {
+            transport_node = "";
+        }
+
+        var device_info = ("<DeviceInterface>" +
+                           "<DeviceUUID>%s</DeviceUUID>" +
+                           "<FriendlyName>%s</FriendlyName>" +
+                           "<NetworkInterface>" +
+                           "<SystemName>%s</SystemName>" +
+                           "<MacAddress>%s</MacAddress>" +
+                           "<InterfaceType>%s</InterfaceType>" +
+                           "<NetworkInterfaceMode>%s</NetworkInterfaceMode>" +
+                           "<AssociatedIpAddresses>%s</AssociatedIpAddresses>" +
+                           "<WakeOnPattern>%s</WakeOnPattern>" +
+                           "%s" +
+                           "</NetworkInterface>" +
+                           "</DeviceInterface>").printf
+                                        (this.root_device.udn,
+                                         this.root_device.get_friendly_name (),
+                                         iface,
+                                         mac_address,
+                                         type,
+                                         mode,
+                                         associated_ips,
+                                         wake_pattern,
+                                         transport_node);
+
+        return TEMPLATE.printf (device_info);
+    }
+
+    private string create_proxied_network_interface_info () {
+        /* No proxy support: Return empty NetworkInterfaceInfo */
+
+        return TEMPLATE.printf ("");
+    }
+
+    private void query_network_interface_info_cb (Service   em,
+                                                  string    var,
+                                                  ref Value val) {
+        val.init (typeof (string));
+        val.set_string (this.create_network_interface_info ());
+    }
+
+    private void query_proxied_network_interface_info_cb (Service   em,
+                                                          string    var,
+                                                          ref Value val) {
+        val.init (typeof (string));
+        val.set_string (this.create_proxied_network_interface_info ());
+    }
+
+    private void get_interface_info_cb (Service       em,
+                                        ServiceAction action) {
+        if (action.get_argument_count () != 0) {
+            action.return_error (402, _("Invalid argument"));
+
+            return;
+        }
+
+        action.set ("NetworkInterfaceInfo",
+                    typeof (string),
+                    this.create_network_interface_info ());
+
+        action.set ("ProxiedNetworkInterfaceInfo",
+                    typeof (string),
+                    this.create_proxied_network_interface_info ());
+
+        action.return ();
+    }
+}
diff --git a/src/librygel-core/rygel-plugin.vala b/src/librygel-core/rygel-plugin.vala
index ddaa842..4059cc7 100644
--- a/src/librygel-core/rygel-plugin.vala
+++ b/src/librygel-core/rygel-plugin.vala
@@ -194,6 +194,23 @@ public class Rygel.Plugin : GUPnP.ResourceFactory {
                                ICON_SMALL_WIDTH,
                                ICON_SMALL_HEIGHT,
                                ICON_JPG_DEPTH);
+
+        /* Enable EnergyManagement service on this device if needed */
+        var config = MetaConfig.get_default ();
+        try {
+            if (config.get_bool (this.name, "energy-management")) {
+                var resource = new ResourceInfo (EnergyManagement.UPNP_ID,
+                                                 EnergyManagement.UPNP_TYPE,
+                                                 EnergyManagement.DESCRIPTION_PATH,
+                                                 typeof (EnergyManagement));
+                this.add_resource (resource);
+
+            }
+        } catch (GLib.Error error) {
+            if (!(error is ConfigurationError.NO_VALUE_SET))
+                warning ("Failed to read configuration: %s", error.message);
+        }
+
     }
 
     public void add_resource (ResourceInfo resource_info) {


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]