/* * Copyright (C) 2007 Zeeshan Ali. * Copyright (C) 2007 OpenedHand Ltd. * * Author: Zeeshan Ali * Author: Jorn Baayen * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU 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. */ #include #include #include #include #include #include #include #ifdef HAVE_CONFIG_H #include #endif #include "gui.h" #include "upnp.h" #include "main.h" #define DESCRIPTION_DOC "xml/network-light-desc.xml" #define DIMMING_SERVICE "urn:schemas-upnp-org:service:Dimming:1" #define SWITCH_SERVICE "urn:schemas-upnp-org:service:SwitchPower:1" #define NETWORK_LIGHT "urn:schemas-upnp-org:device:DimmableLight:1" typedef struct { GUPnPRootDevice *dev; GUPnPServiceInfo *switch_power; GUPnPServiceInfo *dimming; } NetworkLight; static GUPnPContextManager *context_manager; static GHashTable *nl_hash; /* Other network light services on the network */ static GList *switch_proxies; static GList *dimming_proxies; static GUPnPXMLDoc *doc; static char *desc_location; static char *uuid; void on_get_status (GUPnPService *service, GUPnPServiceAction *action, gpointer user_data); void on_get_target (GUPnPService *service, GUPnPServiceAction *action, gpointer user_data); void on_set_target (GUPnPService *service, GUPnPServiceAction *action, gpointer user_data); void on_query_status (GUPnPService *service, const char *variable_name, GValue *value, gpointer user_data); void on_query_target (GUPnPService *service, const char *variable_name, GValue *value, gpointer user_data); void on_get_load_level_status (GUPnPService *service, GUPnPServiceAction *action, gpointer user_data); void on_get_load_level_target (GUPnPService *service, GUPnPServiceAction *action, gpointer user_data); void on_set_load_level_target (GUPnPService *service, GUPnPServiceAction *action, gpointer user_data); void on_query_load_level_status (GUPnPService *service, const char *variable_name, GValue *value, gpointer user_data); void on_query_load_level_target (GUPnPService *service, const char *variable_name, GValue *value, gpointer user_data); static NetworkLight * network_light_new (GUPnPRootDevice *dev, GUPnPServiceInfo *switch_power, GUPnPServiceInfo *dimming) { NetworkLight *network_light; network_light = g_slice_new (NetworkLight); network_light->dev = dev; network_light->switch_power = switch_power; network_light->dimming = dimming; return network_light; } static void network_light_free (NetworkLight *network_light) { g_object_unref (network_light->dev); g_object_unref (network_light->switch_power); g_object_unref (network_light->dimming); g_slice_free (NetworkLight, network_light); } void notify_status_change (gboolean status) { GList *network_lights; GList *nl_node; network_lights = g_hash_table_get_values (nl_hash); for (nl_node = network_lights; nl_node != NULL; nl_node = nl_node->next) { NetworkLight *nl = (NetworkLight *) nl_node->data; gupnp_service_notify (GUPNP_SERVICE (nl->switch_power), "Status", G_TYPE_BOOLEAN, status, NULL); } g_list_free (network_lights); } void notify_load_level_change (gint load_level) { GList *network_lights; GList *nl_node; network_lights = g_hash_table_get_values (nl_hash); for (nl_node = network_lights; nl_node != NULL; nl_node = nl_node->next) { NetworkLight *nl = (NetworkLight *) nl_node->data; gupnp_service_notify (GUPNP_SERVICE (nl->dimming), "LoadLevelStatus", G_TYPE_UINT, load_level, NULL); } g_list_free (network_lights); } G_MODULE_EXPORT void on_get_status (GUPnPService *service, GUPnPServiceAction *action, gpointer user_data) { gupnp_service_action_set (action, "ResultStatus", G_TYPE_BOOLEAN, get_status (), NULL); gupnp_service_action_return (action); } G_MODULE_EXPORT void on_get_target (GUPnPService *service, GUPnPServiceAction *action, gpointer user_data) { gupnp_service_action_set (action, "RetTargetValue", G_TYPE_BOOLEAN, get_status (), NULL); gupnp_service_action_return (action); } G_MODULE_EXPORT void on_set_target (GUPnPService *service, GUPnPServiceAction *action, gpointer user_data) { gboolean status; gupnp_service_action_get (action, "newTargetValue", G_TYPE_BOOLEAN, &status, NULL); gupnp_service_action_return (action); set_status (status); } G_MODULE_EXPORT void on_query_status (GUPnPService *service, const char *variable_name, GValue *value, gpointer user_data) { g_value_init (value, G_TYPE_BOOLEAN); g_value_set_boolean (value, get_status ()); } G_MODULE_EXPORT void on_query_target (GUPnPService *service, const char *variable_name, GValue *value, gpointer user_data) { g_value_init (value, G_TYPE_BOOLEAN); g_value_set_boolean (value, get_status ()); } G_MODULE_EXPORT void on_get_load_level_status (GUPnPService *service, GUPnPServiceAction *action, gpointer user_data) { gupnp_service_action_set (action, "retLoadlevelStatus", G_TYPE_UINT, get_load_level (), NULL); gupnp_service_action_return (action); } G_MODULE_EXPORT void on_get_load_level_target (GUPnPService *service, GUPnPServiceAction *action, gpointer user_data) { gupnp_service_action_set (action, "retLoadlevelTarget", G_TYPE_UINT, get_load_level (), NULL); gupnp_service_action_return (action); } G_MODULE_EXPORT void on_set_load_level_target (GUPnPService *service, GUPnPServiceAction *action, gpointer user_data) { guint load_level; gupnp_service_action_get (action, "newLoadlevelTarget", G_TYPE_UINT, &load_level, NULL); gupnp_service_action_return (action); if (load_level > 100) load_level = 100; set_load_level (load_level); } G_MODULE_EXPORT void on_query_load_level_status (GUPnPService *service, const char *variable_name, GValue *value, gpointer user_data) { g_value_init (value, G_TYPE_UINT); g_value_set_uint (value, get_load_level ()); } G_MODULE_EXPORT void on_query_load_level_target (GUPnPService *service, const char *variable_name, GValue *value, gpointer user_data) { g_value_init (value, G_TYPE_UINT); g_value_set_uint (value, get_load_level ()); } static void on_notify_failed (GUPnPService *service, const GList *callback_urls, const GError *reason, gpointer user_data) { GList *url_node; GString *warning; warning = g_string_new (NULL); g_string_printf (warning, "NOTIFY failed for the following client URLs:\n"); for (url_node = (GList *) callback_urls; url_node; url_node = url_node->next) { g_string_append_printf (warning, "%s\n", (char *) url_node->data); } g_string_append_printf (warning, "Reason: %s", reason->message); g_warning ("%s", warning->str); g_string_free (warning, TRUE); } /* Copied from gupnp/libgupnp/xml-utils.c */ static xmlNode * xml_util_get_element (xmlNode *node, ...) { va_list var_args; va_start (var_args, node); while (TRUE) { const char *arg; arg = va_arg (var_args, const char *); if (!arg) break; for (node = node->children; node; node = node->next) { if (node->name == NULL) continue; if (!strcmp (arg, (char *) node->name)) break; } if (!node) break; } va_end (var_args); return node; } static void init_friendly_name (gchar *name) { xmlNode *fdn_node; fdn_node = xml_util_get_element ((xmlNode *) gupnp_xml_doc_get_doc (doc), "root", "device", "friendlyName", NULL); if (fdn_node == NULL) { g_warning ("Failed to find friendly name element" "in device description, " "using default value"); return; } xmlNodeSetContent (fdn_node, (unsigned char *) name); } static void init_uuid (void) { xmlNode *uuid_node; char *udn; uuid = gupnp_get_uuid (); const xmlDoc *xml_doc = gupnp_xml_doc_get_doc (doc); uuid_node = xml_util_get_element ((xmlNode *) xml_doc, "root", "device", "UDN", NULL); if (uuid_node == NULL) { g_critical ("Failed to find UDN element" "in device description"); return; } udn = g_strdup_printf ("uuid:%s", uuid); xmlNodeSetContent (uuid_node, (unsigned char *) udn); g_free (udn); } static void on_service_proxy_action_ret (GObject *object, GAsyncResult *result, gpointer user_data) { GError *error = NULL; gupnp_service_proxy_call_action_finish (GUPNP_SERVICE_PROXY (object), result, &error); if (error != NULL) { GUPnPServiceInfo *info = GUPNP_SERVICE_INFO (object); g_warning ("Failed to call action \"%s\" on \"%s\": %s", (char *) user_data, gupnp_service_info_get_location (info), error->message); g_error_free (error); } g_free (user_data); } void set_all_status (gboolean status) { GList *proxy_node; for (proxy_node = switch_proxies; proxy_node; proxy_node = g_list_next (proxy_node)) { GUPnPServiceProxy *proxy; char *action_name; GUPnPServiceProxyAction *action; proxy = GUPNP_SERVICE_PROXY (proxy_node->data); action_name = g_strdup ("SetTarget"); action = gupnp_service_proxy_action_new (action_name, "newTargetValue", G_TYPE_BOOLEAN, status, NULL); gupnp_service_proxy_call_action_async ( proxy, action, NULL, on_service_proxy_action_ret, action_name); gupnp_service_proxy_action_unref (action); } } void set_all_load_level (gint load_level) { GList *proxy_node; for (proxy_node = dimming_proxies; proxy_node; proxy_node = g_list_next (proxy_node)) { GUPnPServiceProxy *proxy; char *action_name; GUPnPServiceProxyAction *action; proxy = GUPNP_SERVICE_PROXY (proxy_node->data); action_name = g_strdup ("SetLoadLevelTarget"); action = gupnp_service_proxy_action_new (action_name, "newLoadlevelTarget", G_TYPE_UINT, load_level, NULL); gupnp_service_proxy_call_action_async ( proxy, action, NULL, on_service_proxy_action_ret, action_name); gupnp_service_proxy_action_unref (action); } } static void on_network_light_available (GUPnPControlPoint *cp, GUPnPDeviceProxy *light_proxy, gpointer user_data) { GUPnPServiceProxy *switch_proxy; GUPnPServiceProxy *dimming_proxy; GUPnPServiceInfo *info; info = gupnp_device_info_get_service (GUPNP_DEVICE_INFO (light_proxy), SWITCH_SERVICE); switch_proxy = GUPNP_SERVICE_PROXY (info); if (switch_proxy) { switch_proxies = g_list_append (switch_proxies, switch_proxy); } info = gupnp_device_info_get_service (GUPNP_DEVICE_INFO (light_proxy), DIMMING_SERVICE); dimming_proxy = GUPNP_SERVICE_PROXY (info); if (dimming_proxy) { dimming_proxies = g_list_append (dimming_proxies, dimming_proxy); } } static gint compare_service (GUPnPServiceInfo *info1, GUPnPServiceInfo *info2) { const char *udn1; const char *udn2; udn1 = gupnp_service_info_get_udn (info1); udn2 = gupnp_service_info_get_udn (info2); return strcmp (udn1, udn2); } static void remove_service_from_list (GUPnPServiceInfo *info, GList **list) { GList *proxy_node; proxy_node = g_list_find_custom (*list, info, (GCompareFunc) compare_service); if (proxy_node) { g_object_unref (proxy_node->data); *list = g_list_remove (*list, proxy_node->data); } } static void on_network_light_unavailable (GUPnPControlPoint *control_point, GUPnPDeviceProxy *light_proxy, gpointer user_data) { GUPnPServiceInfo *info; info = gupnp_device_info_get_service (GUPNP_DEVICE_INFO (light_proxy), SWITCH_SERVICE); if (info) { remove_service_from_list (info, &switch_proxies); } g_clear_object (&info); info = gupnp_device_info_get_service (GUPNP_DEVICE_INFO (light_proxy), DIMMING_SERVICE); if (info) { remove_service_from_list (info, &dimming_proxies); } g_clear_object (&info); } static gboolean init_server (GUPnPContext *context) { NetworkLight *network_light; GUPnPRootDevice *dev; GUPnPServiceInfo *switch_power; GUPnPServiceInfo *dimming; GError *error = NULL; /* Create root device */ dev = gupnp_root_device_new_full (context, gupnp_resource_factory_get_default (), doc, desc_location, DATA_DIR, &error); if (error != NULL) { g_warning ("Failed to create root device: %s", error->message); g_error_free (error); return FALSE; } switch_power = gupnp_device_info_get_service (GUPNP_DEVICE_INFO (dev), SWITCH_SERVICE); if (switch_power) { gupnp_service_signals_autoconnect (GUPNP_SERVICE (switch_power), NULL, &error); g_signal_connect (switch_power, "notify-failed", G_CALLBACK (on_notify_failed), NULL); } dimming = gupnp_device_info_get_service (GUPNP_DEVICE_INFO (dev), DIMMING_SERVICE); if (dimming) { gupnp_service_signals_autoconnect (GUPNP_SERVICE (dimming), NULL, &error); g_signal_connect (dimming, "notify-failed", G_CALLBACK (on_notify_failed), NULL); } network_light = network_light_new (dev, switch_power, dimming); g_hash_table_insert (nl_hash, g_object_ref (context), network_light); /* Run */ gupnp_root_device_set_available (dev, TRUE); g_print ("Attaching to IP/Host %s on port %d\n", gssdp_client_get_host_ip (GSSDP_CLIENT (context)), gupnp_context_get_port (context)); return TRUE; } static gboolean prepare_desc (gchar *name) { GError *error = NULL; doc = gupnp_xml_doc_new_from_path (DATA_DIR "/" DESCRIPTION_DOC, &error); if (doc == NULL) { g_critical ("Unable to load the XML description file: %s", error->message); g_error_free (error); return FALSE; } if (name && (strlen(name) > 0)) { /* set the friendlyName in the xmlDoc */ init_friendly_name (name); } /* create and set the UUID in the xmlDoc */ init_uuid (); /* saving the xml file to the temporal location with the uuid name */ desc_location = g_strdup_printf ("%s/gupnp-network-light-%s.xml", g_get_tmp_dir (), uuid); g_assert (desc_location != NULL); if (xmlSaveFile (desc_location, (xmlDoc *) gupnp_xml_doc_get_doc (doc)) < 0) { g_print ("Error saving description file to %s.\n", desc_location); g_free (desc_location); g_object_unref (doc); return FALSE; } return TRUE; } static gboolean init_client (GUPnPContext *context) { GUPnPControlPoint *cp; cp = gupnp_control_point_new (context, NETWORK_LIGHT); g_signal_connect (cp, "device-proxy-available", G_CALLBACK (on_network_light_available), NULL); g_signal_connect (cp, "device-proxy-unavailable", G_CALLBACK (on_network_light_unavailable), NULL); gssdp_resource_browser_set_active (GSSDP_RESOURCE_BROWSER (cp), TRUE); /* Let context manager take care of the control point life cycle */ gupnp_context_manager_manage_control_point (context_manager, cp); /* We don't need to keep our own references to the control points */ g_object_unref (cp); return TRUE; } static void on_context_available (GUPnPContextManager *manager, GUPnPContext *context, gpointer user_data) { /* Initialize client-side stuff */ init_client (context); /* Then the server-side stuff */ init_server (context); } static void on_context_unavailable (GUPnPContextManager *manager, GUPnPContext *context, gpointer user_data) { g_print ("Detaching from IP/Host %s and port %d\n", gssdp_client_get_host_ip (GSSDP_CLIENT (context)), gupnp_context_get_port (context)); g_hash_table_remove (nl_hash, context); } static gboolean context_equal (GUPnPContext *context1, GUPnPContext *context2) { return g_ascii_strcasecmp (gssdp_client_get_host_ip (GSSDP_CLIENT (context1)), gssdp_client_get_host_ip (GSSDP_CLIENT (context2))) == 0; } gboolean init_upnp (gchar **interfaces, guint port, gchar *name, gboolean ipv4, gboolean ipv6) { GUPnPWhiteList *white_list; switch_proxies = NULL; dimming_proxies = NULL; nl_hash = g_hash_table_new_full (g_direct_hash, (GEqualFunc) context_equal, g_object_unref, (GDestroyNotify) network_light_free); if (!prepare_desc (name)) { return FALSE; } // Default: Both GSocketFamily family = G_SOCKET_FAMILY_INVALID; if (!ipv4 && ipv6) { g_debug ("Option a"); family = G_SOCKET_FAMILY_IPV6; } else if (ipv4 && !ipv6) { g_debug ("Option b"); family = G_SOCKET_FAMILY_IPV4; } else { g_debug ("Option c"); // Neither? Just do nothing and enable both } context_manager = gupnp_context_manager_create_full (GSSDP_UDA_VERSION_1_0, family, port); g_assert (context_manager != NULL); if (interfaces != NULL) { white_list = gupnp_context_manager_get_white_list (context_manager); gupnp_white_list_add_entryv (white_list, interfaces); gupnp_white_list_set_enabled (white_list, TRUE); } g_signal_connect (context_manager, "context-available", G_CALLBACK (on_context_available), NULL); g_signal_connect (context_manager, "context-unavailable", G_CALLBACK (on_context_unavailable), NULL); return TRUE; } void deinit_upnp (void) { g_object_unref (context_manager); g_hash_table_unref (nl_hash); g_list_foreach (switch_proxies, (GFunc) g_object_unref, NULL); g_list_foreach (dimming_proxies, (GFunc) g_object_unref, NULL); /* Unref the descriptiont doc */ g_object_unref (doc); if (g_remove (desc_location) != 0) g_warning ("error removing %s\n", desc_location); g_free (desc_location); g_free (uuid); uuid = NULL; }