/* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2012 Instituto Nokia de Tecnologia - INdT * Copyright (C) 2014 Google Inc. * Copyright (C) 2017 Red Hat Inc. * * 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. */ #ifdef HAVE_CONFIG_H #include <config.h> #endif #include <ctype.h> #include <stdbool.h> #include <stdlib.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <errno.h> #include <glib.h> #include "gdbus/gdbus.h" #include "lib/bluetooth.h" #include "lib/hci.h" #include "lib/sdp.h" #include "lib/uuid.h" #include "src/dbus-common.h" #include "src/shared/util.h" #include "src/shared/att.h" #include "src/shared/queue.h" #include "src/shared/gatt-db.h" #include "src/shared/gatt-client.h" #include "src/plugin.h" #include "src/adapter.h" #include "src/device.h" #include "src/profile.h" #include "src/service.h" #include "src/log.h" #include "attrib/att.h" #define BATTERY_INTERFACE "org.bluez.Battery1" #define BATT_UUID16 0x180f /* Generic Attribute/Access Service */ struct batt { char *path; /* D-Bus path of device */ struct btd_device *device; struct gatt_db *db; struct bt_gatt_client *client; struct gatt_db_attribute *attr; unsigned int batt_level_cb_id; uint16_t batt_level_io_handle; uint8_t *initial_value; uint8_t percentage; }; static void batt_free(struct batt *batt) { gatt_db_unref(batt->db); bt_gatt_client_unref(batt->client); btd_device_unref(batt->device); g_free (batt->initial_value); g_free(batt); } static void batt_reset(struct batt *batt) { batt->attr = NULL; gatt_db_unref(batt->db); batt->db = NULL; bt_gatt_client_unref(batt->client); batt->client = NULL; g_free (batt->initial_value); batt->initial_value = NULL; if (batt->path) { g_dbus_unregister_interface(btd_get_dbus_connection(), batt->path, BATTERY_INTERFACE); g_free(batt->path); batt->path = NULL; } } static void parse_battery_level(struct batt *batt, const uint8_t *value) { uint8_t percentage; percentage = value[0]; if (batt->percentage != percentage) { batt->percentage = percentage; DBG("Battery Level updated: %d%%", percentage); g_dbus_emit_property_changed(btd_get_dbus_connection(), batt->path, BATTERY_INTERFACE, "Percentage"); } } static void batt_io_value_cb(uint16_t value_handle, const uint8_t *value, uint16_t length, void *user_data) { struct batt *batt = user_data; if (value_handle == batt->batt_level_io_handle) { parse_battery_level(batt, value); } else { g_assert_not_reached(); } } static gboolean property_get_percentage( const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct batt *batt = data; dbus_message_iter_append_basic(iter, DBUS_TYPE_BYTE, &batt->percentage); return TRUE; } static const GDBusPropertyTable battery_properties[] = { { "Percentage", "y", property_get_percentage }, { } }; static void batt_io_ccc_written_cb(uint16_t att_ecode, void *user_data) { struct batt *batt = user_data; if (att_ecode != 0) { error("Battery Level: notifications not enabled %s", att_ecode2str(att_ecode)); return; } if (g_dbus_register_interface(btd_get_dbus_connection(), batt->path, BATTERY_INTERFACE, NULL, NULL, battery_properties, batt, NULL) == FALSE) { error("Unable to register %s interface for %s", BATTERY_INTERFACE, batt->path); batt_reset(batt); return; } parse_battery_level(batt, batt->initial_value); g_free (batt->initial_value); batt->initial_value = NULL; DBG("Battery Level: notification enabled"); } static void read_initial_battery_level_cb(bool success, uint8_t att_ecode, const uint8_t *value, uint16_t length, void *user_data) { struct batt *batt = user_data; if (!success) { DBG("Reading battery level failed with ATT errror: %u", att_ecode); return; } if (!length) return; batt->initial_value = g_memdup(value, length); /* request notify */ batt->batt_level_cb_id = bt_gatt_client_register_notify(batt->client, batt->batt_level_io_handle, batt_io_ccc_written_cb, batt_io_value_cb, batt, NULL); } static void handle_battery_level(struct batt *batt, uint16_t value_handle) { batt->batt_level_io_handle = value_handle; if (!bt_gatt_client_read_value(batt->client, batt->batt_level_io_handle, read_initial_battery_level_cb, batt, NULL)) DBG("Failed to send request to read battery level"); } static bool uuid_cmp(uint16_t u16, const bt_uuid_t *uuid) { bt_uuid_t lhs; bt_uuid16_create(&lhs, u16); return bt_uuid_cmp(&lhs, uuid) == 0; } static void handle_characteristic(struct gatt_db_attribute *attr, void *user_data) { struct batt *batt = user_data; uint16_t value_handle; bt_uuid_t uuid; if (!gatt_db_attribute_get_char_data(attr, NULL, &value_handle, NULL, NULL, &uuid)) { error("Failed to obtain characteristic data"); return; } if (uuid_cmp(GATT_CHARAC_BATTERY_LEVEL, &uuid)) { handle_battery_level(batt, value_handle); } else { char uuid_str[MAX_LEN_UUID_STR]; bt_uuid_to_string(&uuid, uuid_str, sizeof(uuid_str)); DBG("Unsupported characteristic: %s", uuid_str); } } static void handle_batt_service(struct batt *batt) { gatt_db_service_foreach_char(batt->attr, handle_characteristic, batt); } static int batt_probe(struct btd_service *service) { struct btd_device *device = btd_service_get_device(service); struct batt *batt = btd_service_get_user_data(service); char addr[18]; ba2str(device_get_address(device), addr); DBG("BATT profile probe (%s)", addr); /* Ignore, if we were probed for this device already */ if (batt) { error("Profile probed twice for the same device!"); return -1; } batt = g_new0(struct batt, 1); if (!batt) return -1; batt->percentage = -1; batt->device = btd_device_ref(device); btd_service_set_user_data(service, batt); return 0; } static void batt_remove(struct btd_service *service) { struct btd_device *device = btd_service_get_device(service); struct batt *batt; char addr[18]; ba2str(device_get_address(device), addr); DBG("BATT profile remove (%s)", addr); batt = btd_service_get_user_data(service); if (!batt) { error("BATT service not handled by profile"); return; } batt_free(batt); } static void foreach_batt_service(struct gatt_db_attribute *attr, void *user_data) { struct batt *batt = user_data; if (batt->attr) { error("More than one BATT service exists for this device"); return; } batt->attr = attr; handle_batt_service(batt); } static int batt_accept(struct btd_service *service) { struct btd_device *device = btd_service_get_device(service); struct gatt_db *db = btd_device_get_gatt_db(device); struct bt_gatt_client *client = btd_device_get_gatt_client(device); struct batt *batt = btd_service_get_user_data(service); char addr[18]; bt_uuid_t batt_uuid; ba2str(device_get_address(device), addr); DBG("BATT profile accept (%s)", addr); if (!batt) { error("BATT service not handled by profile"); return -1; } batt->db = gatt_db_ref(db); batt->client = bt_gatt_client_clone(client); /* Handle the BATT services */ bt_uuid16_create(&batt_uuid, BATT_UUID16); gatt_db_foreach_service(db, &batt_uuid, foreach_batt_service, batt); if (!batt->attr) { error("BATT attribute not found"); batt_reset(batt); return -1; } batt->path = g_strdup (device_get_path(device)); btd_service_connecting_complete(service, 0); return 0; } static int batt_disconnect(struct btd_service *service) { struct batt *batt = btd_service_get_user_data(service); batt_reset(batt); btd_service_disconnecting_complete(service, 0); return 0; } static struct btd_profile batt_profile = { .name = "batt-profile", .remote_uuid = BATTERY_UUID, .device_probe = batt_probe, .device_remove = batt_remove, .accept = batt_accept, .disconnect = batt_disconnect, .external = true, }; static int batt_init(void) { return btd_profile_register(&batt_profile); } static void batt_exit(void) { btd_profile_unregister(&batt_profile); } BLUETOOTH_PLUGIN_DEFINE(battery, VERSION, BLUETOOTH_PLUGIN_PRIORITY_DEFAULT, batt_init, batt_exit)