blob: 2c3749209b9c9af99d9daa2247de9274f1359666 [file] [log] [blame]
/*
*
* 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 "src/attio.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 {
guint attio_id;
uint16_t value_handle;
int value;
};
struct batt_device {
uint16_t id;
struct btd_device *device;
GAttrib *attrib;
guint attioid;
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;
DBG("BATT 0x%04x UUID: %s properties: %02x",
chr->handle, chr->uuid, chr->properties);
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 void attio_connected_cb(GAttrib *attrib, gpointer user_data)
{
struct batt_device *battdev = user_data;
struct gatt_primary *prim = battdev->batt_primary;
GSList *l;
DBG("BATT connected");
battdev->attrib = g_attrib_ref(attrib);
battdev->level.value = -1;
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);
}
}
static void attio_disconnected_cb(gpointer user_data)
{
struct batt_device *battdev = user_data;
GSList *l;
DBG("BATT disconnected");
if (battdev->level.attio_id > 0) {
g_attrib_unregister(battdev->attrib, battdev->level.attio_id);
battdev->level.attio_id = 0;
}
if (battdev->attrib)
g_attrib_unref(battdev->attrib);
battdev->attrib = NULL;
}
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));
battdev->attioid = btd_device_add_attio_callback(device,
attio_connected_cb,
attio_disconnected_cb,
battdev);
return battdev;
}
static int batt_unregister_device(struct batt_device *battdev)
{
btd_device_remove_attio_callback(battdev->device, battdev->attioid);
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;
DBG("BATT uuid=%s", prim->uuid);
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;
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,
};
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)