| /* |
| * |
| * BlueZ - Bluetooth protocol stack for Linux |
| * |
| * Copyright (C) 2014 Google, Inc. (edjames@google.com) |
| * Copyright (C) 2012 Marcel Holtmann <marcel@holtmann.org> |
| * Copyright (C) 2012 Nordic Semiconductor Inc. |
| * Copyright (C) 2012 Instituto Nokia de Tecnologia - INdT |
| * |
| * 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 |
| * |
| */ |
| |
| /* much lifted from input/hog.c */ |
| |
| /* battery - read battery level */ |
| |
| #ifdef HAVE_CONFIG_H |
| #include <config.h> |
| #endif |
| |
| #include <stdlib.h> |
| #include <stdbool.h> |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <sys/types.h> |
| #include <sys/stat.h> |
| #include <sys/param.h> |
| #include <unistd.h> |
| #include <glib.h> |
| |
| #include "lib/bluetooth.h" |
| #include "lib/sdp.h" |
| #include "lib/uuid.h" |
| |
| #include "src/adapter.h" |
| #include "src/device.h" |
| #include "src/log.h" |
| #include "src/plugin.h" |
| #include "src/profile.h" |
| #include "src/service.h" |
| #include "attrib/att.h" |
| #include "attrib/gattrib.h" |
| #include "attrib/gatt.h" |
| #include "src/shared/util.h" |
| |
| #define BATT_UUID "0000180f-0000-1000-8000-00805f9b34fb" |
| #define BATT_LEVEL_UUID "00002a19-0000-1000-8000-00805f9b34fb" |
| |
| #define BATTERY_DIR "/tmp/batteries" |
| |
| struct service { |
| uint16_t value_handle; |
| int value; |
| }; |
| |
| struct batt_device { |
| uint16_t id; |
| struct btd_device *device; |
| GAttrib *attrib; |
| struct gatt_primary *batt_primary; |
| |
| struct service level; |
| |
| int batt_level; |
| }; |
| |
| static GSList *devices = NULL; |
| |
| static struct batt_device *batt_new_device(struct btd_device *device, |
| uint16_t id) |
| { |
| struct batt_device *battdev; |
| |
| battdev = g_try_new0(struct batt_device, 1); |
| if (!battdev) |
| return NULL; |
| |
| battdev->id = id; |
| battdev->device = btd_device_ref(device); |
| |
| return battdev; |
| } |
| |
| static void batt_free_device(struct batt_device *battdev) |
| { |
| btd_device_unref(battdev->device); |
| g_attrib_unref(battdev->attrib); |
| g_free(battdev->batt_primary); |
| g_free(battdev); |
| } |
| |
| static time_t wallclock(void) |
| { |
| struct timespec ts; |
| |
| if (clock_gettime(CLOCK_MONOTONIC, &ts) != 0) { |
| return 0; |
| } |
| return ts.tv_sec; |
| } |
| |
| /* should really be done in the dbus agent, I think */ |
| static void batt_update_level_file(struct batt_device* battdev) |
| { |
| char path[MAXPATHLEN], pathnew[MAXPATHLEN]; |
| |
| const char* addr = NULL; |
| const char* id = device_get_path(battdev->device); |
| |
| /* id == /org/bluez/hci0/dev_D0_5F_B8_29_10_8D */ |
| |
| if (id != NULL) { |
| addr = rindex(id, '/'); |
| if (addr == NULL) { |
| addr = id; |
| } else { |
| addr++; |
| } |
| } |
| if (addr == NULL || *addr == '\0') { |
| addr = "UNKNOWN"; |
| } |
| |
| snprintf(path, sizeof path, "%s/%s", BATTERY_DIR, addr); |
| snprintf(pathnew, sizeof pathnew, "%s.new", path); |
| |
| if (mkdir(BATTERY_DIR, 0777) < 0 && errno != EEXIST) { |
| error("BATT mkdir: %s: %s", BATTERY_DIR, strerror(errno)); |
| return; |
| } |
| if (chmod(BATTERY_DIR, 0755) < 0) { |
| error("BATT chmod: %s: %s", BATTERY_DIR, strerror(errno)); |
| } |
| |
| FILE* fp = fopen(pathnew, "w"); |
| if (fp == NULL) { |
| error("BATT fopen: %s: %s", pathnew, strerror(errno)); |
| return; |
| } |
| if (fchmod(fileno(fp), 0644) < 0) { |
| error("BATT chmod: %s: %s", pathnew, strerror(errno)); |
| } |
| fprintf(fp, "%d\n", battdev->batt_level); |
| fclose(fp); |
| |
| if (rename(pathnew, path) != 0) { |
| error("BATT rename: %s -> %s: %s", pathnew, path, strerror(errno)); |
| return; |
| } |
| } |
| |
| static void level_read_char_cb(guint8 status, const guint8 *pdu, guint16 len, |
| gpointer user_data) |
| { |
| struct batt_device *battdev = user_data; |
| |
| if (status != 0) { |
| error("BATT %s failed: %s", __func__, att_ecode2str(status)); |
| return; |
| } |
| battdev->batt_level = pdu[1]; |
| DBG("BATT level=%d", battdev->batt_level); |
| batt_update_level_file(battdev); |
| } |
| |
| static void check_level(struct batt_device *battdev) |
| { |
| if (battdev->level.value_handle == 0) |
| return; |
| |
| gatt_read_char(battdev->attrib, battdev->level.value_handle, |
| level_read_char_cb, battdev); |
| } |
| |
| static void char_discovered_cb(uint8_t status, GSList *chars, void* user_data) |
| { |
| struct batt_device *battdev = user_data; |
| struct gatt_primary *prim = battdev->batt_primary; |
| bt_uuid_t img_identity_uuid, img_block_uuid; |
| GSList *l; |
| uint16_t info_handle = 0, proto_mode_handle = 0; |
| |
| DBG("BATT inspecting characteristics"); |
| if (status != 0) { |
| error("BATT %s failed: %s", __func__, att_ecode2str(status)); |
| return; |
| } |
| |
| for (l = chars; l; l = g_slist_next(l)) { |
| struct gatt_char *chr, *next; |
| bt_uuid_t uuid; |
| uint16_t start, end; |
| |
| chr = l->data; |
| next = l->next ? l->next->data : NULL; |
| |
| start = chr->value_handle + 1; |
| end = (next ? next->handle - 1 : prim->range.end); |
| |
| if (g_strcmp0(chr->uuid, BATT_LEVEL_UUID) == 0) { |
| battdev->level.value_handle = chr->value_handle; |
| DBG("BATT found BATT_LEVEL_UUID value_handle=0x%04x", chr->value_handle); |
| check_level(battdev); |
| } |
| } |
| } |
| |
| static int batt_accept(struct btd_service *service) |
| { |
| struct batt_device *battdev = btd_service_get_user_data(service); |
| struct gatt_primary *prim = battdev->batt_primary; |
| struct btd_device *device = btd_service_get_device(service); |
| GAttrib *attrib = btd_device_get_attrib(device); |
| |
| battdev->attrib = g_attrib_ref(attrib); |
| battdev->level.value = -1; |
| |
| btd_service_connecting_complete(service, 0); |
| |
| if (battdev->level.value_handle == 0) { |
| DBG("BATT discovering characteristics"); |
| |
| gatt_discover_char(battdev->attrib, prim->range.start, |
| prim->range.end, NULL, |
| char_discovered_cb, battdev); |
| } else { |
| check_level(battdev); |
| } |
| |
| return 0; |
| } |
| |
| static int batt_disconnect(struct btd_service *service) |
| { |
| struct batt_device *battdev = btd_service_get_user_data(service); |
| |
| if (battdev->attrib) { |
| g_attrib_unref(battdev->attrib); |
| battdev->attrib = NULL; |
| } |
| |
| btd_service_disconnecting_complete(service, 0); |
| |
| return 0; |
| } |
| |
| static struct batt_device *batt_register_device(struct btd_device *device, |
| struct gatt_primary *prim) |
| { |
| struct batt_device *battdev; |
| GIOCondition cond = G_IO_IN | G_IO_ERR | G_IO_NVAL; |
| GIOChannel *io; |
| |
| battdev = batt_new_device(device, prim->range.start); |
| if (!battdev) |
| return NULL; |
| |
| battdev->batt_primary = g_memdup(prim, sizeof(*prim)); |
| |
| return battdev; |
| } |
| |
| static int batt_unregister_device(struct batt_device *battdev) |
| { |
| batt_free_device(battdev); |
| |
| return 0; |
| } |
| |
| static int batt_probe(struct btd_service *service) |
| { |
| struct btd_device *device = btd_service_get_device(service); |
| const char *path = device_get_path(device); |
| GSList *primaries, *l; |
| |
| DBG("BATT path %s", path); |
| |
| primaries = btd_device_get_primaries(device); |
| if (primaries == NULL) |
| return -EINVAL; |
| |
| for (l = primaries; l; l = g_slist_next(l)) { |
| struct gatt_primary *prim = l->data; |
| struct batt_device *battdev; |
| |
| if (strcmp(prim->uuid, BATT_UUID) != 0) |
| continue; |
| |
| DBG("BATT matched BATT_UUID %s", prim->uuid); |
| battdev = batt_register_device(device, prim); |
| if (battdev == NULL) |
| continue; |
| |
| btd_service_set_user_data(service, battdev); |
| devices = g_slist_append(devices, battdev); |
| } |
| |
| return 0; |
| } |
| |
| static void remove_device(gpointer a, gpointer b) |
| { |
| struct batt_device *battdev = a; |
| struct btd_device *device = b; |
| |
| if (battdev->device != device) |
| return; |
| |
| devices = g_slist_remove(devices, battdev); |
| batt_unregister_device(battdev); |
| } |
| |
| static void batt_remove(struct btd_service *service) |
| { |
| struct btd_device *device = btd_service_get_device(service); |
| const char *path = device_get_path(device); |
| |
| DBG("BATT path %s", path); |
| |
| g_slist_foreach(devices, remove_device, device); |
| } |
| |
| static struct btd_profile batt_profile = { |
| .name = "Battery", |
| .remote_uuid = BATT_UUID, |
| .device_probe = batt_probe, |
| .device_remove = batt_remove, |
| .accept = batt_accept, |
| .disconnect = batt_disconnect, |
| .auto_connect = true, |
| }; |
| |
| static int batt_init(void) |
| { |
| int err; |
| |
| return btd_profile_register(&batt_profile); |
| } |
| |
| static void batt_exit(void) |
| { |
| btd_profile_unregister(&batt_profile); |
| } |
| |
| BLUETOOTH_PLUGIN_DEFINE(battery, VERSION, BLUETOOTH_PLUGIN_PRIORITY_LOW, |
| batt_init, batt_exit) |