/* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2011 Nokia Corporation * Copyright (C) 2011 Marcel Holtmann * * * 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 #endif #include #include #include #include #include #include #include #include #include #include #include "lib/uuid.h" #include "src/dbus-common.h" #include "src/adapter.h" #include "src/device.h" #include "src/error.h" #include "src/log.h" #include "attrib/att.h" #include "attrib/gattrib.h" #include "attrib/gatt.h" #include "src/attio.h" #include "src/textfile.h" #include "monitor.h" #define PROXIMITY_INTERFACE "org.bluez.ProximityMonitor1" #define ALERT_LEVEL_CHR_UUID 0x2A06 #define POWER_LEVEL_CHR_UUID 0x2A07 #define IMMEDIATE_TIMEOUT 5 #define TX_POWER_SIZE 1 enum { ALERT_NONE = 0, ALERT_MILD, ALERT_HIGH, }; struct monitor { struct btd_device *device; GAttrib *attrib; 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 GSList *monitors = NULL; static struct monitor *find_monitor(struct btd_device *device) { GSList *l; for (l = monitors; l; l = l->next) { struct monitor *monitor = l->data; if (monitor->device == device) return monitor; } return NULL; } static void write_proximity_config(struct btd_device *device, const char *alert, const char *level) { char *filename; GKeyFile *key_file; char *data; gsize length = 0; filename = btd_device_get_storage_path(device, "proximity"); if (!filename) { warn("Unable to get proximity storage path for device"); return; } key_file = g_key_file_new(); g_key_file_load_from_file(key_file, filename, 0, NULL); if (level) g_key_file_set_string(key_file, alert, "Level", level); else g_key_file_remove_group(key_file, alert, NULL); data = g_key_file_to_data(key_file, &length, NULL); if (length > 0) { create_file(filename, S_IRUSR | S_IWUSR); g_file_set_contents(filename, data, length, NULL); } g_free(data); g_free(filename); g_key_file_free(key_file); } static char *read_proximity_config(struct btd_device *device, const char *alert) { char *filename; GKeyFile *key_file; char *str; filename = btd_device_get_storage_path(device, "proximity"); if (!filename) { warn("Unable to get proximity storage path for device"); return NULL; } key_file = g_key_file_new(); g_key_file_load_from_file(key_file, filename, 0, NULL); str = g_key_file_get_string(key_file, alert, "Level", NULL); g_free(filename); g_key_file_free(key_file); return str; } 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"); g_dbus_emit_property_changed(btd_get_dbus_connection(), path, PROXIMITY_INTERFACE, "LinkLossAlertLevel"); } static void char_discovered_cb(uint8_t status, GSList *characteristics, void *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[TX_POWER_SIZE]; ssize_t vlen; if (status != 0) { DBG("Tx Power Level read failed: %s", att_ecode2str(status)); return; } vlen = dec_read_resp(pdu, plen, value, sizeof(value)); if (vlen < 0) { DBG("Protocol error"); return; } if (vlen != 1) { DBG("Invalid length for TX Power value: %zd", vlen); return; } DBG("Tx Power Level: %02x", (int8_t) value[0]); } static void tx_power_handle_cb(uint8_t status, GSList *characteristics, void *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, 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, 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"); g_dbus_emit_property_changed(btd_get_dbus_connection(), path, PROXIMITY_INTERFACE, "ImmediateAlertLevel"); 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; g_dbus_emit_property_changed(btd_get_dbus_connection(), path, PROXIMITY_INTERFACE, "ImmediateAlertLevel"); 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(uint8_t status, GSList *characteristics, void *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"); g_dbus_emit_property_changed(btd_get_dbus_connection(), path, PROXIMITY_INTERFACE, "ImmediateAlertLevel"); } 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 gboolean property_get_link_loss_level(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct monitor *monitor = data; dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &monitor->linklosslevel); return TRUE; } static void property_set_link_loss_level(const GDBusPropertyTable *property, DBusMessageIter *iter, GDBusPendingPropertySet id, void *data) { struct monitor *monitor = data; const char *level; if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_STRING) { g_dbus_pending_property_error(id, ERROR_INTERFACE ".InvalidArguments", "Invalid arguments in method call"); return; } dbus_message_iter_get_basic(iter, &level); if (!level_is_valid(level)) { g_dbus_pending_property_error(id, ERROR_INTERFACE ".InvalidArguments", "Invalid arguments in method call"); return; } if (g_strcmp0(monitor->linklosslevel, level) == 0) goto done; g_free(monitor->linklosslevel); monitor->linklosslevel = g_strdup(level); write_proximity_config(monitor->device, "LinkLossAlertLevel", level); if (monitor->attrib) write_alert_level(monitor); done: g_dbus_pending_property_success(id); } static gboolean property_exists_link_loss_level( const GDBusPropertyTable *property, void *data) { struct monitor *monitor = data; if (!monitor->enabled.linkloss) return FALSE; return TRUE; } static gboolean property_get_immediate_alert_level( const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct monitor *monitor = data; dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &monitor->immediatelevel); return TRUE; } static void property_set_immediate_alert_level( const GDBusPropertyTable *property, DBusMessageIter *iter, GDBusPendingPropertySet id, void *data) { struct monitor *monitor = data; struct btd_device *device = monitor->device; const char *level; if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_STRING) { g_dbus_pending_property_error(id, ERROR_INTERFACE ".InvalidArguments", "Invalid arguments in method call"); return; } dbus_message_iter_get_basic(iter, &level); if (!level_is_valid(level)) { g_dbus_pending_property_error(id, ERROR_INTERFACE ".InvalidArguments", "Invalid arguments in method call"); return; } if (g_strcmp0(monitor->immediatelevel, level) == 0) goto done; 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(device, attio_connected_cb, attio_disconnected_cb, monitor); else if (monitor->attrib) write_immediate_alert(monitor); done: g_dbus_pending_property_success(id); } static gboolean property_exists_immediate_alert_level( const GDBusPropertyTable *property, void *data) { struct monitor *monitor = data; if (!(monitor->enabled.findme || monitor->enabled.pathloss)) return FALSE; return TRUE; } static gboolean property_get_signal_level( const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct monitor *monitor = data; dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &monitor->signallevel); return TRUE; } static gboolean property_exists_signal_level(const GDBusPropertyTable *property, void *data) { struct monitor *monitor = data; if (!monitor->enabled.pathloss) return FALSE; return TRUE; } static const GDBusPropertyTable monitor_device_properties[] = { { "LinkLossAlertLevel", "s", property_get_link_loss_level, property_set_link_loss_level, property_exists_link_loss_level }, { "ImmediateAlertLevel", "s", property_get_immediate_alert_level, property_set_immediate_alert_level, property_exists_immediate_alert_level }, { "SignalLevel", "s", property_get_signal_level, NULL, property_exists_signal_level }, { } }; static void monitor_destroy(gpointer user_data) { struct monitor *monitor = user_data; btd_device_unref(monitor->device); g_free(monitor->linklosslevel); g_free(monitor->immediatelevel); g_free(monitor->signallevel); g_free(monitor); monitors = g_slist_remove(monitors, monitor); } static struct monitor *register_monitor(struct btd_device *device) { const char *path = device_get_path(device); struct monitor *monitor; char *level; monitor = find_monitor(device); if (monitor != NULL) return monitor; level = read_proximity_config(device, "LinkLossAlertLevel"); monitor = g_new0(struct monitor, 1); monitor->device = btd_device_ref(device); monitor->linklosslevel = (level ? : g_strdup("high")); monitor->signallevel = g_strdup("unknown"); monitor->immediatelevel = g_strdup("none"); monitors = g_slist_append(monitors, monitor); if (g_dbus_register_interface(btd_get_dbus_connection(), path, PROXIMITY_INTERFACE, NULL, NULL, monitor_device_properties, monitor, monitor_destroy) == FALSE) { error("D-Bus failed to register %s interface", PROXIMITY_INTERFACE); monitor_destroy(monitor); return NULL; } DBG("Registered interface %s on path %s", PROXIMITY_INTERFACE, path); return monitor; } static void update_monitor(struct monitor *monitor) { if (monitor->txpower != NULL && monitor->immediate != NULL) monitor->enabled.pathloss = TRUE; else monitor->enabled.pathloss = FALSE; 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) return; if (monitor->attioid != 0) return; monitor->attioid = btd_device_add_attio_callback(monitor->device, attio_connected_cb, attio_disconnected_cb, monitor); } int monitor_register_linkloss(struct btd_device *device, struct enabled *enabled, struct gatt_primary *linkloss) { struct monitor *monitor; if (!enabled->linkloss) return 0; monitor = register_monitor(device); if (monitor == NULL) return -1; monitor->linkloss = g_new0(struct att_range, 1); monitor->linkloss->start = linkloss->range.start; monitor->linkloss->end = linkloss->range.end; monitor->enabled.linkloss = TRUE; update_monitor(monitor); return 0; } int monitor_register_txpower(struct btd_device *device, struct enabled *enabled, struct gatt_primary *txpower) { struct monitor *monitor; if (!enabled->pathloss) return 0; monitor = register_monitor(device); if (monitor == NULL) return -1; monitor->txpower = g_new0(struct att_range, 1); monitor->txpower->start = txpower->range.start; monitor->txpower->end = txpower->range.end; update_monitor(monitor); return 0; } int monitor_register_immediate(struct btd_device *device, struct enabled *enabled, struct gatt_primary *immediate) { struct monitor *monitor; if (!enabled->pathloss && !enabled->findme) return 0; monitor = register_monitor(device); if (monitor == NULL) return -1; 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; update_monitor(monitor); return 0; } static void cleanup_monitor(struct monitor *monitor) { struct btd_device *device = monitor->device; const char *path = device_get_path(device); if (monitor->immediate != NULL || monitor->txpower != NULL) return; if (monitor->immediateto != 0) { g_source_remove(monitor->immediateto); monitor->immediateto = 0; } if (monitor->linkloss != NULL) return; if (monitor->attioid != 0) { btd_device_remove_attio_callback(device, monitor->attioid); monitor->attioid = 0; } if (monitor->attrib != NULL) { g_attrib_unref(monitor->attrib); monitor->attrib = NULL; } g_dbus_unregister_interface(btd_get_dbus_connection(), path, PROXIMITY_INTERFACE); } void monitor_unregister_linkloss(struct btd_device *device) { struct monitor *monitor; monitor = find_monitor(device); if (monitor == NULL) return; g_free(monitor->linkloss); monitor->linkloss = NULL; monitor->enabled.linkloss = FALSE; cleanup_monitor(monitor); } void monitor_unregister_txpower(struct btd_device *device) { struct monitor *monitor; monitor = find_monitor(device); if (monitor == NULL) return; g_free(monitor->txpower); monitor->txpower = NULL; monitor->enabled.pathloss = FALSE; cleanup_monitor(monitor); } void monitor_unregister_immediate(struct btd_device *device) { struct monitor *monitor; monitor = find_monitor(device); if (monitor == NULL) return; g_free(monitor->immediate); monitor->immediate = NULL; monitor->enabled.findme = FALSE; monitor->enabled.pathloss = FALSE; cleanup_monitor(monitor); }