diff options
author | Anas Nashif <anas.nashif@intel.com> | 2012-10-30 12:51:59 -0700 |
---|---|---|
committer | Anas Nashif <anas.nashif@intel.com> | 2012-10-30 12:51:59 -0700 |
commit | 71235d13fb107f879369e64385d8c97de0bb840e (patch) | |
tree | 6610983e8decd3f2543f4d05c8e0794b260769ee /proximity | |
download | bluez-71235d13fb107f879369e64385d8c97de0bb840e.tar.gz bluez-71235d13fb107f879369e64385d8c97de0bb840e.tar.bz2 bluez-71235d13fb107f879369e64385d8c97de0bb840e.zip |
Imported Upstream version 4.101upstream/4.101
Diffstat (limited to 'proximity')
-rw-r--r-- | proximity/immalert.c | 290 | ||||
-rw-r--r-- | proximity/immalert.h | 26 | ||||
-rw-r--r-- | proximity/linkloss.c | 338 | ||||
-rw-r--r-- | proximity/linkloss.h | 26 | ||||
-rw-r--r-- | proximity/main.c | 96 | ||||
-rw-r--r-- | proximity/manager.c | 150 | ||||
-rw-r--r-- | proximity/manager.h | 26 | ||||
-rw-r--r-- | proximity/monitor.c | 667 | ||||
-rw-r--r-- | proximity/monitor.h | 34 | ||||
-rw-r--r-- | proximity/proximity.conf | 9 | ||||
-rw-r--r-- | proximity/reporter.c | 306 | ||||
-rw-r--r-- | proximity/reporter.h | 42 |
12 files changed, 2010 insertions, 0 deletions
diff --git a/proximity/immalert.c b/proximity/immalert.c new file mode 100644 index 00000000..1540b613 --- /dev/null +++ b/proximity/immalert.c @@ -0,0 +1,290 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2012 Texas Instruments Corporation + * + * 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 St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <glib.h> +#include <bluetooth/uuid.h> +#include <adapter.h> + +#include <dbus/dbus.h> +#include <gdbus.h> + +#include "log.h" +#include "gattrib.h" +#include "att.h" +#include "gatt.h" +#include "att-database.h" +#include "gatt-service.h" +#include "attrib-server.h" +#include "device.h" +#include "attio.h" +#include "dbus-common.h" +#include "reporter.h" +#include "immalert.h" + +struct imm_alert_adapter { + struct btd_adapter *adapter; + DBusConnection *conn; + GSList *connected_devices; +}; + +struct connected_device { + struct btd_device *device; + struct imm_alert_adapter *adapter; + uint8_t alert_level; + guint callback_id; +}; + +static GSList *imm_alert_adapters; + +static int imdevice_cmp(gconstpointer a, gconstpointer b) +{ + const struct connected_device *condev = a; + const struct btd_device *device = b; + + if (condev->device == device) + return 0; + + return -1; +} + +static struct connected_device * +find_connected_device(struct imm_alert_adapter *ia, struct btd_device *device) +{ + GSList *l = g_slist_find_custom(ia->connected_devices, device, + imdevice_cmp); + if (!l) + return NULL; + + return l->data; +} + +static int imadapter_cmp(gconstpointer a, gconstpointer b) +{ + const struct imm_alert_adapter *imadapter = a; + const struct btd_adapter *adapter = b; + + if (imadapter->adapter == adapter) + return 0; + + return -1; +} + +static struct imm_alert_adapter * +find_imm_alert_adapter(struct btd_adapter *adapter) +{ + GSList *l = g_slist_find_custom(imm_alert_adapters, adapter, + imadapter_cmp); + if (!l) + return NULL; + + return l->data; +} + +const char *imm_alert_get_level(struct btd_device *device) +{ + struct imm_alert_adapter *imadapter; + struct connected_device *condev; + + if (!device) + return get_alert_level_string(NO_ALERT); + + imadapter = find_imm_alert_adapter(device_get_adapter(device)); + if (!imadapter) + return get_alert_level_string(NO_ALERT); + + condev = find_connected_device(imadapter, device); + if (!condev) + return get_alert_level_string(NO_ALERT); + + return get_alert_level_string(condev->alert_level); +} + +static void imm_alert_emit_alert_signal(struct connected_device *condev, + uint8_t alert_level) +{ + struct imm_alert_adapter *adapter; + const char *path, *alert_level_str; + + if (!condev) + return; + + adapter = condev->adapter; + path = device_get_path(condev->device); + alert_level_str = get_alert_level_string(alert_level); + + DBG("alert %s remote %s", alert_level_str, path); + + emit_property_changed(adapter->conn, path, + PROXIMITY_REPORTER_INTERFACE, "ImmediateAlertLevel", + DBUS_TYPE_STRING, &alert_level_str); +} + +static void imm_alert_remove_condev(struct connected_device *condev) +{ + struct imm_alert_adapter *ia; + + if (!condev) + return; + + ia = condev->adapter; + + if (condev->callback_id && condev->device) + btd_device_remove_attio_callback(condev->device, + condev->callback_id); + + if (condev->device) + btd_device_unref(condev->device); + + ia->connected_devices = g_slist_remove(ia->connected_devices, condev); + g_free(condev); +} + +/* condev can be NULL */ +static void imm_alert_disc_cb(gpointer user_data) +{ + struct connected_device *condev = user_data; + + if (!condev) + return; + + DBG("immediate alert remove device %p", condev->device); + + imm_alert_emit_alert_signal(condev, NO_ALERT); + imm_alert_remove_condev(condev); +} + +static uint8_t imm_alert_alert_lvl_write(struct attribute *a, + struct btd_device *device, gpointer user_data) +{ + uint8_t value; + struct imm_alert_adapter *ia = user_data; + struct connected_device *condev = NULL; + + if (!device) + goto set_error; + + condev = find_connected_device(ia, device); + + if (a->len == 0) { + DBG("Illegal alert level length"); + goto set_error; + } + + value = a->data[0]; + if (value != NO_ALERT && value != MILD_ALERT && value != HIGH_ALERT) { + DBG("Illegal alert value"); + goto set_error; + } + + /* Register a disconnect cb if the alert level is non-zero */ + if (value != NO_ALERT && !condev) { + condev = g_new0(struct connected_device, 1); + condev->device = btd_device_ref(device); + condev->adapter = ia; + condev->callback_id = btd_device_add_attio_callback(device, + NULL, imm_alert_disc_cb, condev); + ia->connected_devices = g_slist_append(ia->connected_devices, + condev); + DBG("added connected dev %p", device); + } + + if (value != NO_ALERT) { + condev->alert_level = value; + imm_alert_emit_alert_signal(condev, value); + } + + /* + * Emit NO_ALERT if the alert level was non-zero before. This is + * guaranteed when there's a condev. + */ + if (value == NO_ALERT && condev) + imm_alert_disc_cb(condev); + + DBG("alert level set to %d by device %p", value, device); + return 0; + +set_error: + error("Set immediate alert level for dev %p", device); + /* remove alerts by erroneous devices */ + imm_alert_disc_cb(condev); + return ATT_ECODE_IO; +} + +void imm_alert_register(struct btd_adapter *adapter, DBusConnection *conn) +{ + gboolean svc_added; + bt_uuid_t uuid; + struct imm_alert_adapter *imadapter; + + bt_uuid16_create(&uuid, IMMEDIATE_ALERT_SVC_UUID); + + imadapter = g_new0(struct imm_alert_adapter, 1); + imadapter->adapter = adapter; + imadapter->conn = dbus_connection_ref(conn); + + imm_alert_adapters = g_slist_append(imm_alert_adapters, imadapter); + + /* Immediate Alert Service */ + svc_added = gatt_service_add(adapter, + GATT_PRIM_SVC_UUID, &uuid, + /* Alert level characteristic */ + GATT_OPT_CHR_UUID, ALERT_LEVEL_CHR_UUID, + GATT_OPT_CHR_PROPS, + ATT_CHAR_PROPER_WRITE_WITHOUT_RESP, + GATT_OPT_CHR_VALUE_CB, ATTRIB_WRITE, + imm_alert_alert_lvl_write, imadapter, + GATT_OPT_INVALID); + + if (!svc_added) { + imm_alert_unregister(adapter); + return; + } + + DBG("Immediate Alert service added"); +} + +static void remove_condev_list_item(gpointer data, gpointer user_data) +{ + struct connected_device *condev = data; + + imm_alert_remove_condev(condev); +} + +void imm_alert_unregister(struct btd_adapter *adapter) +{ + struct imm_alert_adapter *imadapter; + + imadapter = find_imm_alert_adapter(adapter); + if (!imadapter) + return; + + g_slist_foreach(imadapter->connected_devices, remove_condev_list_item, + NULL); + dbus_connection_unref(imadapter->conn); + + imm_alert_adapters = g_slist_remove(imm_alert_adapters, imadapter); + g_free(imadapter); +} diff --git a/proximity/immalert.h b/proximity/immalert.h new file mode 100644 index 00000000..dd28eaab --- /dev/null +++ b/proximity/immalert.h @@ -0,0 +1,26 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2012 Texas Instruments Corporation + * + * + * 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 St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +void imm_alert_register(struct btd_adapter *adapter, DBusConnection *conn); +void imm_alert_unregister(struct btd_adapter *adapter); +const char *imm_alert_get_level(struct btd_device *device); diff --git a/proximity/linkloss.c b/proximity/linkloss.c new file mode 100644 index 00000000..14403cbc --- /dev/null +++ b/proximity/linkloss.c @@ -0,0 +1,338 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2012 Texas Instruments Corporation + * + * 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 St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <glib.h> +#include <bluetooth/uuid.h> +#include <adapter.h> + +#include <dbus/dbus.h> +#include <gdbus.h> + +#include "log.h" +#include "att-database.h" +#include "gattrib.h" +#include "att.h" +#include "gatt.h" +#include "gatt-service.h" +#include "attrib-server.h" +#include "device.h" +#include "attio.h" +#include "dbus-common.h" +#include "reporter.h" +#include "linkloss.h" + +#define BLUEZ_SERVICE "org.bluez" + +struct link_loss_adapter { + struct btd_adapter *adapter; + uint16_t alert_lvl_value_handle; + DBusConnection *conn; + GSList *connected_devices; +}; + +struct connected_device { + struct btd_device *device; + struct link_loss_adapter *adapter; + uint8_t alert_level; + guint callback_id; + guint local_disc_id; +}; + +static GSList *link_loss_adapters; + +static int lldevice_cmp(gconstpointer a, gconstpointer b) +{ + const struct connected_device *llcondev = a; + const struct btd_device *device = b; + + if (llcondev->device == device) + return 0; + + return -1; +} + +static struct connected_device * +find_connected_device(struct link_loss_adapter *la, struct btd_device *device) +{ + GSList *l = g_slist_find_custom(la->connected_devices, device, + lldevice_cmp); + if (!l) + return NULL; + + return l->data; +} + +static int lladapter_cmp(gconstpointer a, gconstpointer b) +{ + const struct link_loss_adapter *lladapter = a; + const struct btd_adapter *adapter = b; + + if (lladapter->adapter == adapter) + return 0; + + return -1; +} + +static struct link_loss_adapter * +find_link_loss_adapter(struct btd_adapter *adapter) +{ + GSList *l = g_slist_find_custom(link_loss_adapters, adapter, + lladapter_cmp); + if (!l) + return NULL; + + return l->data; +} + +const char *link_loss_get_alert_level(struct btd_device *device) +{ + struct link_loss_adapter *lladapter; + struct connected_device *condev; + + if (!device) + return get_alert_level_string(NO_ALERT); + + lladapter = find_link_loss_adapter(device_get_adapter(device)); + if (!lladapter) + return get_alert_level_string(NO_ALERT); + + condev = find_connected_device(lladapter, device); + if (!condev) + return get_alert_level_string(NO_ALERT); + + return get_alert_level_string(condev->alert_level); +} + +static void link_loss_emit_alert_signal(struct connected_device *condev) +{ + struct link_loss_adapter *adapter = condev->adapter; + const char *alert_level_str, *path; + + if (!condev->device) + return; + + path = device_get_path(condev->device); + alert_level_str = get_alert_level_string(condev->alert_level); + + DBG("alert %s remote %s", alert_level_str, path); + + emit_property_changed(adapter->conn, path, + PROXIMITY_REPORTER_INTERFACE, "LinkLossAlertLevel", + DBUS_TYPE_STRING, &alert_level_str); +} + +static uint8_t link_loss_alert_lvl_read(struct attribute *a, + struct btd_device *device, gpointer user_data) +{ + struct link_loss_adapter *la = user_data; + struct connected_device *condev; + uint8_t alert_level = NO_ALERT; + + if (!device) + goto out; + + condev = find_connected_device(la, device); + if (!condev) + goto out; + + alert_level = condev->alert_level; + +out: + DBG("return alert level %d for dev %p", alert_level, device); + + /* update the alert level according to the requesting device */ + attrib_db_update(la->adapter, a->handle, NULL, &alert_level, + sizeof(alert_level), NULL); + + return 0; +} + +/* condev can be NULL */ +static void link_loss_remove_condev(struct connected_device *condev) +{ + struct link_loss_adapter *la; + + if (!condev) + return; + + la = condev->adapter; + + if (condev->callback_id && condev->device) + btd_device_remove_attio_callback(condev->device, + condev->callback_id); + + if (condev->local_disc_id && condev->device) + device_remove_disconnect_watch(condev->device, + condev->local_disc_id); + + if (condev->device) + btd_device_unref(condev->device); + + la->connected_devices = g_slist_remove(la->connected_devices, condev); + g_free(condev); +} + +static void link_loss_disc_cb(gpointer user_data) +{ + struct connected_device *condev = user_data; + + DBG("alert loss disconnect device %p", condev->device); + + /* if an alert-level is set, emit a signal */ + if (condev->alert_level != NO_ALERT) + link_loss_emit_alert_signal(condev); + + /* we are open for more changes now */ + link_loss_remove_condev(condev); +} + +static void link_loss_local_disc(struct btd_device *device, + gboolean removal, void *user_data) +{ + struct connected_device *condev = user_data; + + /* no need to alert on this device - we requested disconnection */ + link_loss_remove_condev(condev); + + DBG("alert level zeroed for locally disconnecting dev %p", device); +} + +static uint8_t link_loss_alert_lvl_write(struct attribute *a, + struct btd_device *device, gpointer user_data) +{ + uint8_t value; + struct link_loss_adapter *la = user_data; + struct connected_device *condev = NULL; + + if (!device) + goto set_error; + + /* condev might remain NULL here if nothing is found */ + condev = find_connected_device(la, device); + + if (a->len == 0) { + DBG("Illegal alert level length"); + goto set_error; + } + + value = a->data[0]; + if (value != NO_ALERT && value != MILD_ALERT && value != HIGH_ALERT) { + DBG("Illegal alert value"); + goto set_error; + } + + /* Register a disconnect cb if the alert level is non-zero */ + if (value != NO_ALERT && !condev) { + condev = g_new0(struct connected_device, 1); + condev->device = btd_device_ref(device); + condev->adapter = la; + condev->callback_id = btd_device_add_attio_callback(device, + NULL, link_loss_disc_cb, condev); + condev->local_disc_id = device_add_disconnect_watch(device, + link_loss_local_disc, condev, NULL); + + la->connected_devices = g_slist_append(la->connected_devices, + condev); + } else if (value == NO_ALERT && condev) { + link_loss_remove_condev(condev); + condev = NULL; + } + + DBG("alert level set to %d by device %p", value, device); + + if (condev) + condev->alert_level = value; + + return 0; + +set_error: + error("Set link loss alert level for dev %p", device); + /* reset alert level on erroneous devices */ + link_loss_remove_condev(condev); + return ATT_ECODE_IO; +} + +void link_loss_register(struct btd_adapter *adapter, DBusConnection *conn) +{ + gboolean svc_added; + bt_uuid_t uuid; + struct link_loss_adapter *lladapter; + + bt_uuid16_create(&uuid, LINK_LOSS_SVC_UUID); + + lladapter = g_new0(struct link_loss_adapter, 1); + lladapter->adapter = adapter; + lladapter->conn = dbus_connection_ref(conn); + + link_loss_adapters = g_slist_append(link_loss_adapters, lladapter); + + /* Link Loss Service */ + svc_added = gatt_service_add(adapter, + GATT_PRIM_SVC_UUID, &uuid, + /* Alert level characteristic */ + GATT_OPT_CHR_UUID, ALERT_LEVEL_CHR_UUID, + GATT_OPT_CHR_PROPS, + ATT_CHAR_PROPER_READ | ATT_CHAR_PROPER_WRITE, + GATT_OPT_CHR_VALUE_CB, ATTRIB_READ, + link_loss_alert_lvl_read, lladapter, + GATT_OPT_CHR_VALUE_CB, ATTRIB_WRITE, + link_loss_alert_lvl_write, lladapter, + GATT_OPT_CHR_VALUE_GET_HANDLE, + &lladapter->alert_lvl_value_handle, + GATT_OPT_INVALID); + + if (!svc_added) + goto err; + + DBG("Link Loss service added"); + return; + +err: + error("Error adding Link Loss service"); + link_loss_unregister(adapter); +} + +static void remove_condev_list_item(gpointer data, gpointer user_data) +{ + struct connected_device *condev = data; + + link_loss_remove_condev(condev); +} + +void link_loss_unregister(struct btd_adapter *adapter) +{ + struct link_loss_adapter *lladapter; + lladapter = find_link_loss_adapter(adapter); + if (!lladapter) + return; + + g_slist_foreach(lladapter->connected_devices, remove_condev_list_item, + NULL); + dbus_connection_unref(lladapter->conn); + + link_loss_adapters = g_slist_remove(link_loss_adapters, lladapter); + g_free(lladapter); +} diff --git a/proximity/linkloss.h b/proximity/linkloss.h new file mode 100644 index 00000000..a7d83d02 --- /dev/null +++ b/proximity/linkloss.h @@ -0,0 +1,26 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2012 Texas Instruments Corporation + * + * + * 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 St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +void link_loss_register(struct btd_adapter *adapter, DBusConnection *conn); +void link_loss_unregister(struct btd_adapter *adapter); +const char *link_loss_get_alert_level(struct btd_device *device); diff --git a/proximity/main.c b/proximity/main.c new file mode 100644 index 00000000..3d5d9b2d --- /dev/null +++ b/proximity/main.c @@ -0,0 +1,96 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2011 Nokia Corporation + * Copyright (C) 2011 Marcel Holtmann <marcel@holtmann.org> + * + * + * 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 St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <errno.h> +#include <stdint.h> +#include <glib.h> +#include <gdbus.h> + +#include "log.h" +#include "plugin.h" +#include "manager.h" +#include "hcid.h" + +static DBusConnection *connection = NULL; +static GKeyFile *config = NULL; + +static GKeyFile *open_config_file(const char *file) +{ + GError *gerr = NULL; + GKeyFile *keyfile; + + keyfile = g_key_file_new(); + + g_key_file_set_list_separator(keyfile, ','); + + if (!g_key_file_load_from_file(keyfile, file, 0, &gerr)) { + error("Parsing %s failed: %s", file, gerr->message); + g_error_free(gerr); + g_key_file_free(keyfile); + return NULL; + } + + return keyfile; +} + +static int proximity_init(void) +{ + if (!main_opts.gatt_enabled) { + DBG("GATT is disabled"); + return -ENOTSUP; + } + + connection = dbus_bus_get(DBUS_BUS_SYSTEM, NULL); + if (connection == NULL) + return -EIO; + + config = open_config_file(CONFIGDIR "/proximity.conf"); + + if (proximity_manager_init(connection, config) < 0) { + dbus_connection_unref(connection); + return -EIO; + } + + return 0; +} + +static void proximity_exit(void) +{ + if (!main_opts.gatt_enabled) + return; + + if (config) + g_key_file_free(config); + + proximity_manager_exit(); + dbus_connection_unref(connection); +} + +BLUETOOTH_PLUGIN_DEFINE(proximity, VERSION, + BLUETOOTH_PLUGIN_PRIORITY_DEFAULT, + proximity_init, proximity_exit) diff --git a/proximity/manager.c b/proximity/manager.c new file mode 100644 index 00000000..f2e49a6c --- /dev/null +++ b/proximity/manager.c @@ -0,0 +1,150 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2011 Nokia Corporation + * Copyright (C) 2011 Marcel Holtmann <marcel@holtmann.org> + * + * + * 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 St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <glib.h> +#include <gdbus.h> +#include <bluetooth/uuid.h> + +#include "adapter.h" +#include "device.h" +#include "att.h" +#include "gattrib.h" +#include "gatt.h" +#include "monitor.h" +#include "reporter.h" +#include "manager.h" + +static DBusConnection *connection = NULL; + +static struct enabled enabled = { + .linkloss = TRUE, + .pathloss = TRUE, + .findme = TRUE, +}; + +static gint primary_uuid_cmp(gconstpointer a, gconstpointer b) +{ + const struct gatt_primary *prim = a; + const char *uuid = b; + + return g_strcmp0(prim->uuid, uuid); +} + +static int attio_device_probe(struct btd_device *device, GSList *uuids) +{ + struct gatt_primary *linkloss, *txpower, *immediate; + GSList *l, *primaries; + + primaries = btd_device_get_primaries(device); + + l = g_slist_find_custom(primaries, IMMEDIATE_ALERT_UUID, + primary_uuid_cmp); + immediate = (l ? l->data : NULL); + + l = g_slist_find_custom(primaries, TX_POWER_UUID, primary_uuid_cmp); + txpower = (l ? l->data : NULL); + + l = g_slist_find_custom(primaries, LINK_LOSS_UUID, primary_uuid_cmp); + linkloss = (l ? l->data : NULL); + + return monitor_register(connection, device, linkloss, txpower, + immediate, &enabled); +} + +static void attio_device_remove(struct btd_device *device) +{ + monitor_unregister(connection, device); +} + +static struct btd_device_driver monitor_driver = { + .name = "Proximity GATT Monitor Driver", + .uuids = BTD_UUIDS(IMMEDIATE_ALERT_UUID, LINK_LOSS_UUID, TX_POWER_UUID), + .probe = attio_device_probe, + .remove = attio_device_remove, +}; + +static struct btd_adapter_driver reporter_server_driver = { + .name = "Proximity GATT Reporter Driver", + .probe = reporter_init, + .remove = reporter_exit, +}; + +static void load_config_file(GKeyFile *config) +{ + char **list; + int i; + + if (config == NULL) + return; + + list = g_key_file_get_string_list(config, "General", "Disable", + NULL, NULL); + for (i = 0; list && list[i] != NULL; i++) { + if (g_str_equal(list[i], "FindMe")) + enabled.findme = FALSE; + else if (g_str_equal(list[i], "LinkLoss")) + enabled.linkloss = FALSE; + else if (g_str_equal(list[i], "PathLoss")) + enabled.pathloss = FALSE; + } + + g_strfreev(list); +} + +int proximity_manager_init(DBusConnection *conn, GKeyFile *config) +{ + int ret; + + load_config_file(config); + + connection = dbus_connection_ref(conn); + + ret = btd_register_device_driver(&monitor_driver); + if (ret < 0) + goto fail_monitor; + + ret = btd_register_adapter_driver(&reporter_server_driver); + if (ret < 0) + goto fail_reporter; + + return 0; + +fail_reporter: + btd_unregister_device_driver(&monitor_driver); + +fail_monitor: + dbus_connection_unref(connection); + return ret; +} + +void proximity_manager_exit(void) +{ + btd_unregister_device_driver(&monitor_driver); + btd_unregister_adapter_driver(&reporter_server_driver); + dbus_connection_unref(connection); +} diff --git a/proximity/manager.h b/proximity/manager.h new file mode 100644 index 00000000..b0fe7c8c --- /dev/null +++ b/proximity/manager.h @@ -0,0 +1,26 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2011 Nokia Corporation + * Copyright (C) 2011 Marcel Holtmann <marcel@holtmann.org> + * + * + * 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 St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +int proximity_manager_init(DBusConnection *conn, GKeyFile *conf); +void proximity_manager_exit(void); diff --git a/proximity/monitor.c b/proximity/monitor.c new file mode 100644 index 00000000..98dbcd10 --- /dev/null +++ b/proximity/monitor.c @@ -0,0 +1,667 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2011 Nokia Corporation + * Copyright (C) 2011 Marcel Holtmann <marcel@holtmann.org> + * + * + * 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 St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <errno.h> +#include <fcntl.h> +#include <gdbus.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <sys/stat.h> + +#include <bluetooth/bluetooth.h> +#include <bluetooth/uuid.h> + +#include "dbus-common.h" +#include "adapter.h" +#include "device.h" +#include "error.h" +#include "log.h" +#include "att.h" +#include "gattrib.h" +#include "gatt.h" +#include "attio.h" +#include "monitor.h" +#include "textfile.h" + +#define PROXIMITY_INTERFACE "org.bluez.ProximityMonitor" + +#define ALERT_LEVEL_CHR_UUID 0x2A06 +#define POWER_LEVEL_CHR_UUID 0x2A07 + +#define IMMEDIATE_TIMEOUT 5 + +enum { + ALERT_NONE = 0, + ALERT_MILD, + ALERT_HIGH, +}; + +struct monitor { + struct btd_device *device; + GAttrib *attrib; + DBusConnection *conn; + struct att_range *linkloss; + struct att_range *txpower; + struct att_range *immediate; + struct enabled enabled; + char *linklosslevel; /* Link Loss Alert Level */ + char *fallbacklevel; /* Immediate fallback alert level */ + char *immediatelevel; /* Immediate Alert Level */ + char *signallevel; /* Path Loss RSSI level */ + uint16_t linklosshandle; /* Link Loss Characteristic + * Value Handle */ + uint16_t txpowerhandle; /* Tx Characteristic Value Handle */ + uint16_t immediatehandle; /* Immediate Alert Value Handle */ + guint immediateto; /* Reset Immediate Alert to "none" */ + guint attioid; +}; + +static inline int create_filename(char *buf, size_t size, + const bdaddr_t *bdaddr, const char *name) +{ + char addr[18]; + + ba2str(bdaddr, addr); + + return create_name(buf, size, STORAGEDIR, addr, name); +} + +static int write_proximity_config(bdaddr_t *sba, bdaddr_t *dba, + const char *alert, const char *level) +{ + char filename[PATH_MAX + 1], addr[18], key[38]; + + create_filename(filename, PATH_MAX, sba, "proximity"); + + create_file(filename, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); + + ba2str(dba, addr); + + snprintf(key, sizeof(key), "%17s#%s", addr, alert); + + return textfile_put(filename, key, level); +} + +static char *read_proximity_config(bdaddr_t *sba, bdaddr_t *dba, + const char *alert) +{ + char filename[PATH_MAX + 1], addr[18], key[38]; + char *str, *strnew; + + create_filename(filename, PATH_MAX, sba, "proximity"); + + ba2str(dba, addr); + snprintf(key, sizeof(key), "%17s#%s", addr, alert); + + str = textfile_caseget(filename, key); + if (str == NULL) + return NULL; + + strnew = g_strdup(str); + free(str); + + return strnew; +} + +static uint8_t str2level(const char *level) +{ + if (g_strcmp0("high", level) == 0) + return ALERT_HIGH; + else if (g_strcmp0("mild", level) == 0) + return ALERT_MILD; + + return ALERT_NONE; +} + +static void linkloss_written(guint8 status, const guint8 *pdu, guint16 plen, + gpointer user_data) +{ + struct monitor *monitor = user_data; + struct btd_device *device = monitor->device; + const char *path = device_get_path(device); + + if (status != 0) { + error("Link Loss Write Request failed: %s", + att_ecode2str(status)); + return; + } + + if (!dec_write_resp(pdu, plen)) { + error("Link Loss Write Request: protocol error"); + return; + } + + DBG("Link Loss Alert Level written"); + + emit_property_changed(monitor->conn, path, + PROXIMITY_INTERFACE, "LinkLossAlertLevel", + DBUS_TYPE_STRING, &monitor->linklosslevel); +} + +static void char_discovered_cb(GSList *characteristics, guint8 status, + gpointer user_data) +{ + struct monitor *monitor = user_data; + struct gatt_char *chr; + uint8_t value = str2level(monitor->linklosslevel); + + if (status) { + error("Discover Link Loss handle: %s", att_ecode2str(status)); + return; + } + + DBG("Setting alert level \"%s\" on Reporter", monitor->linklosslevel); + + /* Assume there is a single Alert Level characteristic */ + chr = characteristics->data; + monitor->linklosshandle = chr->value_handle; + + gatt_write_char(monitor->attrib, monitor->linklosshandle, &value, 1, + linkloss_written, monitor); +} + +static int write_alert_level(struct monitor *monitor) +{ + struct att_range *linkloss = monitor->linkloss; + bt_uuid_t uuid; + + if (monitor->linklosshandle) { + uint8_t value = str2level(monitor->linklosslevel); + + gatt_write_char(monitor->attrib, monitor->linklosshandle, + &value, 1, linkloss_written, monitor); + return 0; + } + + bt_uuid16_create(&uuid, ALERT_LEVEL_CHR_UUID); + + /* FIXME: use cache (requires service changed support) ? */ + gatt_discover_char(monitor->attrib, linkloss->start, linkloss->end, + &uuid, char_discovered_cb, monitor); + + return 0; +} + +static void tx_power_read_cb(guint8 status, const guint8 *pdu, guint16 plen, + gpointer user_data) +{ + uint8_t value[ATT_MAX_MTU]; + int vlen; + + if (status != 0) { + DBG("Tx Power Level read failed: %s", att_ecode2str(status)); + return; + } + + if (!dec_read_resp(pdu, plen, value, &vlen)) { + DBG("Protocol error"); + return; + } + + if (vlen != 1) { + DBG("Invalid length for TX Power value: %d", vlen); + return; + } + + DBG("Tx Power Level: %02x", (int8_t) value[0]); +} + +static void tx_power_handle_cb(GSList *characteristics, guint8 status, + gpointer user_data) +{ + struct monitor *monitor = user_data; + struct gatt_char *chr; + + if (status) { + error("Discover Tx Power handle: %s", att_ecode2str(status)); + return; + } + + chr = characteristics->data; + monitor->txpowerhandle = chr->value_handle; + + DBG("Tx Power handle: 0x%04x", monitor->txpowerhandle); + + gatt_read_char(monitor->attrib, monitor->txpowerhandle, 0, + tx_power_read_cb, monitor); +} + +static void read_tx_power(struct monitor *monitor) +{ + struct att_range *txpower = monitor->txpower; + bt_uuid_t uuid; + + if (monitor->txpowerhandle != 0) { + gatt_read_char(monitor->attrib, monitor->txpowerhandle, 0, + tx_power_read_cb, monitor); + return; + } + + bt_uuid16_create(&uuid, POWER_LEVEL_CHR_UUID); + + gatt_discover_char(monitor->attrib, txpower->start, txpower->end, + &uuid, tx_power_handle_cb, monitor); +} + +static gboolean immediate_timeout(gpointer user_data) +{ + struct monitor *monitor = user_data; + const char *path = device_get_path(monitor->device); + + monitor->immediateto = 0; + + if (g_strcmp0(monitor->immediatelevel, "none") == 0) + return FALSE; + + if (monitor->attrib) { + uint8_t value = ALERT_NONE; + gatt_write_cmd(monitor->attrib, monitor->immediatehandle, + &value, 1, NULL, NULL); + } + + g_free(monitor->immediatelevel); + monitor->immediatelevel = g_strdup("none"); + emit_property_changed(monitor->conn, path, PROXIMITY_INTERFACE, + "ImmediateAlertLevel", DBUS_TYPE_STRING, + &monitor->immediatelevel); + + return FALSE; +} + +static void immediate_written(gpointer user_data) +{ + struct monitor *monitor = user_data; + const char *path = device_get_path(monitor->device); + + g_free(monitor->fallbacklevel); + monitor->fallbacklevel = NULL; + + emit_property_changed(monitor->conn, path, PROXIMITY_INTERFACE, + "ImmediateAlertLevel", + DBUS_TYPE_STRING, &monitor->immediatelevel); + + monitor->immediateto = g_timeout_add_seconds(IMMEDIATE_TIMEOUT, + immediate_timeout, monitor); +} + +static void write_immediate_alert(struct monitor *monitor) +{ + uint8_t value = str2level(monitor->immediatelevel); + + gatt_write_cmd(monitor->attrib, monitor->immediatehandle, &value, 1, + immediate_written, monitor); +} + +static void immediate_handle_cb(GSList *characteristics, guint8 status, + gpointer user_data) +{ + struct monitor *monitor = user_data; + struct gatt_char *chr; + + if (status) { + error("Discover Immediate Alert handle: %s", + att_ecode2str(status)); + return; + } + + chr = characteristics->data; + monitor->immediatehandle = chr->value_handle; + + DBG("Immediate Alert handle: 0x%04x", monitor->immediatehandle); + + if (monitor->fallbacklevel) + write_immediate_alert(monitor); +} + +static void discover_immediate_handle(struct monitor *monitor) +{ + struct att_range *immediate = monitor->immediate; + bt_uuid_t uuid; + + bt_uuid16_create(&uuid, ALERT_LEVEL_CHR_UUID); + + gatt_discover_char(monitor->attrib, immediate->start, immediate->end, + &uuid, immediate_handle_cb, monitor); +} + +static void attio_connected_cb(GAttrib *attrib, gpointer user_data) +{ + struct monitor *monitor = user_data; + + monitor->attrib = g_attrib_ref(attrib); + + if (monitor->enabled.linkloss) + write_alert_level(monitor); + + if (monitor->enabled.pathloss) + read_tx_power(monitor); + + if (monitor->immediatehandle == 0) { + if(monitor->enabled.pathloss || monitor->enabled.findme) + discover_immediate_handle(monitor); + } else if (monitor->fallbacklevel) + write_immediate_alert(monitor); +} + +static void attio_disconnected_cb(gpointer user_data) +{ + struct monitor *monitor = user_data; + const char *path = device_get_path(monitor->device); + + g_attrib_unref(monitor->attrib); + monitor->attrib = NULL; + + if (monitor->immediateto == 0) + return; + + g_source_remove(monitor->immediateto); + monitor->immediateto = 0; + + if (g_strcmp0(monitor->immediatelevel, "none") == 0) + return; + + g_free(monitor->immediatelevel); + monitor->immediatelevel = g_strdup("none"); + emit_property_changed(monitor->conn, path, PROXIMITY_INTERFACE, + "ImmediateAlertLevel", DBUS_TYPE_STRING, + &monitor->immediatelevel); +} + +static gboolean level_is_valid(const char *level) +{ + return (g_str_equal("none", level) || + g_str_equal("mild", level) || + g_str_equal("high", level)); +} + +static DBusMessage *set_link_loss_alert(DBusConnection *conn, DBusMessage *msg, + const char *level, void *data) +{ + struct monitor *monitor = data; + struct btd_device *device = monitor->device; + bdaddr_t sba, dba; + + if (!level_is_valid(level)) + return btd_error_invalid_args(msg); + + if (g_strcmp0(monitor->linklosslevel, level) == 0) + return dbus_message_new_method_return(msg); + + g_free(monitor->linklosslevel); + monitor->linklosslevel = g_strdup(level); + + adapter_get_address(device_get_adapter(device), &sba); + device_get_address(device, &dba, NULL); + + write_proximity_config(&sba, &dba, "LinkLossAlertLevel", level); + + if (monitor->attrib) + write_alert_level(monitor); + + return dbus_message_new_method_return(msg); +} + +static DBusMessage *set_immediate_alert(DBusConnection *conn, DBusMessage *msg, + const char *level, void *data) +{ + struct monitor *monitor = data; + + if (!level_is_valid(level)) + return btd_error_invalid_args(msg); + + if (g_strcmp0(monitor->immediatelevel, level) == 0) + return dbus_message_new_method_return(msg); + + if (monitor->immediateto) { + g_source_remove(monitor->immediateto); + monitor->immediateto = 0; + } + + /* Previous Immediate Alert level if connection/write fails */ + g_free(monitor->fallbacklevel); + monitor->fallbacklevel = monitor->immediatelevel; + + monitor->immediatelevel = g_strdup(level); + + /* + * Means that Link/Path Loss are disabled or there is a pending + * writting for Find Me(Immediate Alert characteristic value). + * If enabled, Path Loss always registers a connection callback + * when the Proximity Monitor starts. + */ + if (monitor->attioid == 0) + monitor->attioid = btd_device_add_attio_callback(monitor->device, + attio_connected_cb, + attio_disconnected_cb, + monitor); + else if (monitor->attrib) + write_immediate_alert(monitor); + + return dbus_message_new_method_return(msg); +} + +static DBusMessage *get_properties(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + struct monitor *monitor = data; + DBusMessageIter iter; + DBusMessageIter dict; + DBusMessage *reply; + + reply = dbus_message_new_method_return(msg); + if (!reply) + return NULL; + + dbus_message_iter_init_append(reply, &iter); + + dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, + DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING + DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING + DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict); + + if (monitor->enabled.linkloss) + dict_append_entry(&dict, "LinkLossAlertLevel", + DBUS_TYPE_STRING, &monitor->linklosslevel); + + if (monitor->enabled.findme || monitor->enabled.pathloss) + dict_append_entry(&dict, "ImmediateAlertLevel", + DBUS_TYPE_STRING, &monitor->immediatelevel); + + if (monitor->enabled.pathloss) + dict_append_entry(&dict, "SignalLevel", + DBUS_TYPE_STRING, &monitor->signallevel); + + dbus_message_iter_close_container(&iter, &dict); + + return reply; +} + +static DBusMessage *set_property(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + struct monitor *monitor = data; + const char *property; + DBusMessageIter iter; + DBusMessageIter sub; + const char *level; + + if (!dbus_message_iter_init(msg, &iter)) + return btd_error_invalid_args(msg); + + if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING) + return btd_error_invalid_args(msg); + + dbus_message_iter_get_basic(&iter, &property); + dbus_message_iter_next(&iter); + + if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT) + return btd_error_invalid_args(msg); + + dbus_message_iter_recurse(&iter, &sub); + + if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_STRING) + return btd_error_invalid_args(msg); + + dbus_message_iter_get_basic(&sub, &level); + + if (g_str_equal("ImmediateAlertLevel", property)) { + if (monitor->enabled.findme == FALSE && + monitor->enabled.pathloss == FALSE) + return btd_error_not_available(msg); + + return set_immediate_alert(conn, msg, level, data); + } else if (g_str_equal("LinkLossAlertLevel", property)) { + if (monitor->enabled.linkloss == FALSE) + return btd_error_not_available(msg); + + return set_link_loss_alert(conn, msg, level, data); + } + + return btd_error_invalid_args(msg); +} + +static const GDBusMethodTable monitor_methods[] = { + { GDBUS_METHOD("GetProperties", + NULL, GDBUS_ARGS({ "properties", "a{sv}" }), + get_properties) }, + { GDBUS_ASYNC_METHOD("SetProperty", + GDBUS_ARGS({ "name", "s" }, { "value", "v" }), NULL, + set_property) }, + { } +}; + +static const GDBusSignalTable monitor_signals[] = { + { GDBUS_SIGNAL("PropertyChanged", + GDBUS_ARGS({ "name", "s" }, { "value", "v" })) }, + { } +}; + +static void monitor_destroy(gpointer user_data) +{ + struct monitor *monitor = user_data; + + if (monitor->immediateto) + g_source_remove(monitor->immediateto); + + if (monitor->attioid) + btd_device_remove_attio_callback(monitor->device, + monitor->attioid); + if (monitor->attrib) + g_attrib_unref(monitor->attrib); + + dbus_connection_unref(monitor->conn); + btd_device_unref(monitor->device); + g_free(monitor->linkloss); + g_free(monitor->immediate); + g_free(monitor->txpower); + g_free(monitor->linklosslevel); + g_free(monitor->immediatelevel); + g_free(monitor->signallevel); + g_free(monitor); +} + +int monitor_register(DBusConnection *conn, struct btd_device *device, + struct gatt_primary *linkloss, struct gatt_primary *txpower, + struct gatt_primary *immediate, struct enabled *enabled) +{ + const char *path = device_get_path(device); + struct monitor *monitor; + bdaddr_t sba, dba; + char *level; + + adapter_get_address(device_get_adapter(device), &sba); + device_get_address(device, &dba, NULL); + + level = read_proximity_config(&sba, &dba, "LinkLossAlertLevel"); + + monitor = g_new0(struct monitor, 1); + monitor->device = btd_device_ref(device); + monitor->conn = dbus_connection_ref(conn); + monitor->linklosslevel = (level ? : g_strdup("high")); + monitor->signallevel = g_strdup("unknown"); + monitor->immediatelevel = g_strdup("none"); + + if (g_dbus_register_interface(conn, path, + PROXIMITY_INTERFACE, + monitor_methods, monitor_signals, + NULL, monitor, monitor_destroy) == FALSE) { + error("D-Bus failed to register %s interface", + PROXIMITY_INTERFACE); + monitor_destroy(monitor); + return -1; + } + + DBG("Registered interface %s on path %s", PROXIMITY_INTERFACE, path); + + if (linkloss && enabled->linkloss) { + monitor->linkloss = g_new0(struct att_range, 1); + monitor->linkloss->start = linkloss->range.start; + monitor->linkloss->end = linkloss->range.end; + + monitor->enabled.linkloss = TRUE; + } + + if (immediate) { + if (txpower && enabled->pathloss) { + monitor->txpower = g_new0(struct att_range, 1); + monitor->txpower->start = txpower->range.start; + monitor->txpower->end = txpower->range.end; + + monitor->enabled.pathloss = TRUE; + } + + if (enabled->pathloss || enabled->findme) { + monitor->immediate = g_new0(struct att_range, 1); + monitor->immediate->start = immediate->range.start; + monitor->immediate->end = immediate->range.end; + } + + monitor->enabled.findme = enabled->findme; + } + + DBG("Link Loss: %s, Path Loss: %s, FindMe: %s", + monitor->enabled.linkloss ? "TRUE" : "FALSE", + monitor->enabled.pathloss ? "TRUE" : "FALSE", + monitor->enabled.findme ? "TRUE" : "FALSE"); + + if (monitor->enabled.linkloss || monitor->enabled.pathloss) + monitor->attioid = btd_device_add_attio_callback(device, + attio_connected_cb, + attio_disconnected_cb, + monitor); + + return 0; +} + +void monitor_unregister(DBusConnection *conn, struct btd_device *device) +{ + const char *path = device_get_path(device); + + g_dbus_unregister_interface(conn, path, PROXIMITY_INTERFACE); +} diff --git a/proximity/monitor.h b/proximity/monitor.h new file mode 100644 index 00000000..b71363d6 --- /dev/null +++ b/proximity/monitor.h @@ -0,0 +1,34 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2011 Nokia Corporation + * Copyright (C) 2011 Marcel Holtmann <marcel@holtmann.org> + * + * + * 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 St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +struct enabled { + gboolean linkloss; + gboolean pathloss; + gboolean findme; +}; + +int monitor_register(DBusConnection *conn, struct btd_device *device, + struct gatt_primary *linkloss, struct gatt_primary *txpower, + struct gatt_primary *immediate, struct enabled *enabled); +void monitor_unregister(DBusConnection *conn, struct btd_device *device); diff --git a/proximity/proximity.conf b/proximity/proximity.conf new file mode 100644 index 00000000..417610ff --- /dev/null +++ b/proximity/proximity.conf @@ -0,0 +1,9 @@ +# Configuration file for the proximity service + +# This section contains options which are not specific to any +# particular interface +[General] + +# Configuration to allow disabling Proximity services +# Allowed values: LinkLoss,PathLoss,FindMe +Disable=PathLoss diff --git a/proximity/reporter.c b/proximity/reporter.c new file mode 100644 index 00000000..607de2bd --- /dev/null +++ b/proximity/reporter.c @@ -0,0 +1,306 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2011 Nokia Corporation + * Copyright (C) 2011 Marcel Holtmann <marcel@holtmann.org> + * + * + * 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 St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <glib.h> +#include <bluetooth/uuid.h> +#include <adapter.h> +#include <errno.h> + +#include <dbus/dbus.h> +#include <gdbus.h> + +#include "log.h" + +#include "dbus-common.h" +#include "error.h" +#include "device.h" +#include "hcid.h" +#include "gattrib.h" +#include "att.h" +#include "gatt.h" +#include "att-database.h" +#include "attrib-server.h" +#include "reporter.h" +#include "linkloss.h" +#include "immalert.h" + +#define BLUEZ_SERVICE "org.bluez" + +struct reporter_adapter { + DBusConnection *conn; + struct btd_adapter *adapter; + GSList *devices; +}; + +static GSList *reporter_adapters; + +static int radapter_cmp(gconstpointer a, gconstpointer b) +{ + const struct reporter_adapter *radapter = a; + const struct btd_adapter *adapter = b; + + if (radapter->adapter == adapter) + return 0; + + return -1; +} + +static struct reporter_adapter * +find_reporter_adapter(struct btd_adapter *adapter) +{ + GSList *l = g_slist_find_custom(reporter_adapters, adapter, + radapter_cmp); + if (!l) + return NULL; + + return l->data; +} + +const char *get_alert_level_string(uint8_t level) +{ + switch (level) { + case NO_ALERT: + return "none"; + case MILD_ALERT: + return "mild"; + case HIGH_ALERT: + return "high"; + } + + return "unknown"; +} + +static void register_tx_power(struct btd_adapter *adapter) +{ + uint16_t start_handle, h; + const int svc_size = 4; + uint8_t atval[256]; + bt_uuid_t uuid; + + bt_uuid16_create(&uuid, TX_POWER_SVC_UUID); + start_handle = attrib_db_find_avail(adapter, &uuid, svc_size); + if (start_handle == 0) { + error("Not enough free handles to register service"); + return; + } + + DBG("start_handle=0x%04x", start_handle); + + h = start_handle; + + /* Primary service definition */ + bt_uuid16_create(&uuid, GATT_PRIM_SVC_UUID); + att_put_u16(TX_POWER_SVC_UUID, &atval[0]); + attrib_db_add(adapter, h++, &uuid, ATT_NONE, ATT_NOT_PERMITTED, atval, 2); + + /* Power level characteristic */ + bt_uuid16_create(&uuid, GATT_CHARAC_UUID); + atval[0] = ATT_CHAR_PROPER_READ | ATT_CHAR_PROPER_NOTIFY; + att_put_u16(h + 1, &atval[1]); + att_put_u16(POWER_LEVEL_CHR_UUID, &atval[3]); + attrib_db_add(adapter, h++, &uuid, ATT_NONE, ATT_NOT_PERMITTED, atval, 5); + + /* Power level value */ + bt_uuid16_create(&uuid, POWER_LEVEL_CHR_UUID); + att_put_u8(0x00, &atval[0]); + attrib_db_add(adapter, h++, &uuid, ATT_NONE, ATT_NOT_PERMITTED, atval, 1); + + /* Client characteristic configuration */ + bt_uuid16_create(&uuid, GATT_CLIENT_CHARAC_CFG_UUID); + atval[0] = 0x00; + atval[1] = 0x00; + attrib_db_add(adapter, h++, &uuid, ATT_NONE, ATT_NONE, atval, 2); + + g_assert(h - start_handle == svc_size); +} + +static DBusMessage *get_properties(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + DBusMessageIter iter; + DBusMessageIter dict; + DBusMessage *reply = NULL; + const char *linkloss_level, *immalert_level; + struct btd_device *device = data; + + reply = dbus_message_new_method_return(msg); + if (!reply) + return NULL; + + linkloss_level = link_loss_get_alert_level(device); + immalert_level = imm_alert_get_level(device); + + dbus_message_iter_init_append(reply, &iter); + + if (!dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, + DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING + DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING + DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict)) + goto err; + + dict_append_entry(&dict, "LinkLossAlertLevel", DBUS_TYPE_STRING, + &linkloss_level); + dict_append_entry(&dict, "ImmediateAlertLevel", DBUS_TYPE_STRING, + &immalert_level); + + if (!dbus_message_iter_close_container(&iter, &dict)) + goto err; + + return reply; + +err: + if (reply) + dbus_message_unref(reply); + return btd_error_failed(msg, "not enough memory"); +} + +static const GDBusMethodTable reporter_methods[] = { + { GDBUS_METHOD("GetProperties", + NULL, GDBUS_ARGS({ "properties", "a{sv}" }), + get_properties) }, + { } +}; + +static const GDBusSignalTable reporter_signals[] = { + { GDBUS_SIGNAL("PropertyChanged", + GDBUS_ARGS({ "name", "s" }, { "value", "v" })) }, + { } +}; + +static void unregister_reporter_device(gpointer data, gpointer user_data) +{ + struct btd_device *device = data; + struct reporter_adapter *radapter = user_data; + const char *path = device_get_path(device); + + DBG("unregister on device %s", path); + + g_dbus_unregister_interface(radapter->conn, path, + PROXIMITY_REPORTER_INTERFACE); + + radapter->devices = g_slist_remove(radapter->devices, device); + btd_device_unref(device); +} + +static void register_reporter_device(struct btd_device *device, + struct reporter_adapter *radapter) +{ + const char *path = device_get_path(device); + + DBG("register on device %s", path); + + g_dbus_register_interface(radapter->conn, path, + PROXIMITY_REPORTER_INTERFACE, + reporter_methods, reporter_signals, + NULL, device, NULL); + + btd_device_ref(device); + radapter->devices = g_slist_prepend(radapter->devices, device); +} + +static int reporter_device_probe(struct btd_device *device, GSList *uuids) +{ + struct reporter_adapter *radapter; + struct btd_adapter *adapter = device_get_adapter(device); + + radapter = find_reporter_adapter(adapter); + if (!radapter) + return -1; + + register_reporter_device(device, radapter); + return 0; +} + +static void reporter_device_remove(struct btd_device *device) +{ + struct reporter_adapter *radapter; + struct btd_adapter *adapter = device_get_adapter(device); + + radapter = find_reporter_adapter(adapter); + if (!radapter) + return; + + unregister_reporter_device(device, radapter); +} + +/* device driver for tracking remote GATT client devices */ +static struct btd_device_driver reporter_device_driver = { + .name = "Proximity GATT Reporter Device Tracker Driver", + .uuids = BTD_UUIDS(GATT_UUID), + .probe = reporter_device_probe, + .remove = reporter_device_remove, +}; + +int reporter_init(struct btd_adapter *adapter) +{ + struct reporter_adapter *radapter; + DBusConnection *conn; + + if (!main_opts.gatt_enabled) { + DBG("GATT is disabled"); + return -ENOTSUP; + } + + conn = dbus_bus_get(DBUS_BUS_SYSTEM, NULL); + if (!conn) + return -1; + + radapter = g_new0(struct reporter_adapter, 1); + radapter->adapter = adapter; + radapter->conn = conn; + + link_loss_register(adapter, radapter->conn); + register_tx_power(adapter); + imm_alert_register(adapter, radapter->conn); + + btd_register_device_driver(&reporter_device_driver); + + reporter_adapters = g_slist_prepend(reporter_adapters, radapter); + DBG("Proximity Reporter for adapter %p", adapter); + + return 0; +} + +void reporter_exit(struct btd_adapter *adapter) +{ + struct reporter_adapter *radapter = find_reporter_adapter(adapter); + if (!radapter) + return; + + btd_unregister_device_driver(&reporter_device_driver); + + g_slist_foreach(radapter->devices, unregister_reporter_device, + radapter); + + link_loss_unregister(adapter); + imm_alert_unregister(adapter); + dbus_connection_unref(radapter->conn); + + reporter_adapters = g_slist_remove(reporter_adapters, radapter); + g_free(radapter); +} diff --git a/proximity/reporter.h b/proximity/reporter.h new file mode 100644 index 00000000..5ae0eb2f --- /dev/null +++ b/proximity/reporter.h @@ -0,0 +1,42 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2011 Nokia Corporation + * Copyright (C) 2011 Marcel Holtmann <marcel@holtmann.org> + * + * + * 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 St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#define PROXIMITY_REPORTER_INTERFACE "org.bluez.ProximityReporter" + +#define IMMEDIATE_ALERT_SVC_UUID 0x1802 +#define LINK_LOSS_SVC_UUID 0x1803 +#define TX_POWER_SVC_UUID 0x1804 +#define ALERT_LEVEL_CHR_UUID 0x2A06 +#define POWER_LEVEL_CHR_UUID 0x2A07 + +enum { + NO_ALERT = 0x00, + MILD_ALERT = 0x01, + HIGH_ALERT = 0x02, +}; + +int reporter_init(struct btd_adapter *adapter); +void reporter_exit(struct btd_adapter *adapter); + +const char *get_alert_level_string(uint8_t level); |