Hi,
Changes since v3:
* rebased due to profile code moved to separate files
Andrzej Kaczmarek (6):
heartrate: Add attio callbacks
heartrate: Enable measurement when watchers are registered
heartrate: Read Heart Rate Control Point characteristics
heartrate: Handle characteristics value changed notification
heartrate: Process Heart Rate Measurement characteristics
heartrate: Add Reset method
Rafal Garbat (10):
heartrate: Add Heart Rate Profile client
heartrate: Discover characteristics
heartrate: Process characteristics
heartrate: Discover characteristics descriptors
heartrate: Process characteristics descriptors
heartrate: Add HeartRateManager interface
heartrate: Read Body Sensor Location characteristics
heartrate: Add GetProperties method
heartrate: Add HeartRateWatcher interface to default policy
heartrate: Add test script
Santiago Carot-Nemesio (1):
Heart Rate Profile API
Makefile.am | 9 +-
Makefile.tools | 4 +-
doc/heartrate-api.txt | 80 ++++
lib/uuid.h | 5 +
profiles/heartrate/heartrate.c | 969 +++++++++++++++++++++++++++++++++++++++++
profiles/heartrate/heartrate.h | 27 ++
profiles/heartrate/main.c | 52 +++
profiles/heartrate/manager.c | 96 ++++
profiles/heartrate/manager.h | 24 +
src/bluetooth.conf | 1 +
test/test-heartrate | 101 +++++
11 files changed, 1364 insertions(+), 4 deletions(-)
create mode 100644 doc/heartrate-api.txt
create mode 100644 profiles/heartrate/heartrate.c
create mode 100644 profiles/heartrate/heartrate.h
create mode 100644 profiles/heartrate/main.c
create mode 100644 profiles/heartrate/manager.c
create mode 100644 profiles/heartrate/manager.h
create mode 100755 test/test-heartrate
--
1.7.11.3
Hi again,
On 09/13/2012 02:13 PM, Kaczmarek Andrzej wrote:
> Hi,
>
> Changes since v3:
> * rebased due to profile code moved to separate files
>
<snip>
Please ignore v4, I rebased older version of patches by mistake. Will
send proper v5 shortly.
BR,
Andrzej
From: Rafal Garbat <[email protected]>
This patch adds initial support for the Heart Rate Profile client.
Change-Id: I4cff5789a4d37ec1e2fe4c506159fd2ed578da91
---
Makefile.am | 9 ++-
lib/uuid.h | 2 +
profiles/heartrate/heartrate.c | 163 +++++++++++++++++++++++++++++++++++++++++
profiles/heartrate/heartrate.h | 26 +++++++
profiles/heartrate/main.c | 52 +++++++++++++
profiles/heartrate/manager.c | 76 +++++++++++++++++++
profiles/heartrate/manager.h | 24 ++++++
7 files changed, 350 insertions(+), 2 deletions(-)
create mode 100644 profiles/heartrate/heartrate.c
create mode 100644 profiles/heartrate/heartrate.h
create mode 100644 profiles/heartrate/main.c
create mode 100644 profiles/heartrate/manager.c
create mode 100644 profiles/heartrate/manager.h
diff --git a/Makefile.am b/Makefile.am
index 372111a..808a81f 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -211,7 +211,7 @@ endif
if GATTMODULES
builtin_modules += thermometer alert time gatt_example proximity deviceinfo \
- gatt
+ gatt heartrate
builtin_sources += profiles/thermometer/main.c \
profiles/thermometer/manager.h \
profiles/thermometer/manager.c \
@@ -240,7 +240,12 @@ builtin_sources += profiles/thermometer/main.c \
profiles/deviceinfo/deviceinfo.c \
profiles/gatt/main.c profiles/gatt/manager.h \
profiles/gatt/manager.c profiles/gatt/gas.h \
- profiles/gatt/gas.c
+ profiles/gatt/gas.c \
+ profiles/heartrate/main.c \
+ profiles/heartrate/manager.c \
+ profiles/heartrate/manager.h \
+ profiles/heartrate/heartrate.c \
+ profiles/heartrate/heartrate.h
endif
builtin_modules += formfactor
diff --git a/lib/uuid.h b/lib/uuid.h
index aa6efdf..3488e66 100644
--- a/lib/uuid.h
+++ b/lib/uuid.h
@@ -63,6 +63,8 @@ extern "C" {
#define SAP_UUID "0000112D-0000-1000-8000-00805f9b34fb"
+#define HEART_RATE_UUID "0000180d-0000-1000-8000-00805f9b34fb"
+
#define HEALTH_THERMOMETER_UUID "00001809-0000-1000-8000-00805f9b34fb"
#define TEMPERATURE_MEASUREMENT_UUID "00002a1c-0000-1000-8000-00805f9b34fb"
#define TEMPERATURE_TYPE_UUID "00002a1d-0000-1000-8000-00805f9b34fb"
diff --git a/profiles/heartrate/heartrate.c b/profiles/heartrate/heartrate.c
new file mode 100644
index 0000000..06cb12a
--- /dev/null
+++ b/profiles/heartrate/heartrate.c
@@ -0,0 +1,163 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2012 Tieto Poland
+ *
+ * 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 <stdbool.h>
+#include <glib.h>
+#include <bluetooth/uuid.h>
+
+#include "adapter.h"
+#include "device.h"
+#include "heartrate.h"
+
+struct heartrate_adapter {
+ struct btd_adapter *adapter;
+ GSList *devices;
+};
+
+struct heartrate_device {
+ struct btd_device *dev;
+ struct heartrate_adapter *hr;
+};
+
+static GSList *heartrate_adapters = NULL;
+
+static gint cmp_adapter(gconstpointer a, gconstpointer b)
+{
+ const struct heartrate_adapter *hr = a;
+ const struct btd_adapter *adapter = b;
+
+ if (adapter == hr->adapter)
+ return 0;
+
+ return -1;
+}
+
+static gint cmp_device(gconstpointer a, gconstpointer b)
+{
+ const struct heartrate_device *hrdev = a;
+ const struct btd_device *dev = b;
+
+ if (dev == hrdev->dev)
+ return 0;
+
+ return -1;
+}
+
+static struct heartrate_adapter *
+find_heartrate_adapter(struct btd_adapter *adapter)
+{
+ GSList *l = g_slist_find_custom(heartrate_adapters, adapter,
+ cmp_adapter);
+ if (!l)
+ return NULL;
+
+ return l->data;
+}
+
+static void destroy_heartrate_device(gpointer user_data)
+{
+ struct heartrate_device *hrdev = user_data;
+
+ btd_device_unref(hrdev->dev);
+ g_free(hrdev);
+}
+
+static void destroy_heartrate_adapter(gpointer user_data)
+{
+ struct heartrate_adapter *hr = user_data;
+
+ g_free(hr);
+}
+
+int heartrate_adapter_register(struct btd_adapter *adapter)
+{
+ struct heartrate_adapter *hr;
+
+ hr = g_new0(struct heartrate_adapter, 1);
+ hr->adapter = adapter;
+
+ heartrate_adapters = g_slist_prepend(heartrate_adapters, hr);
+
+ return 0;
+}
+void heartrate_adapter_unregister(struct btd_adapter *adapter)
+{
+ struct heartrate_adapter *hr;
+
+ hr = find_heartrate_adapter(adapter);
+ if (hr == NULL)
+ return;
+
+ heartrate_adapters = g_slist_remove(heartrate_adapters, hr);
+
+ destroy_heartrate_adapter(hr);
+}
+
+int heartrate_device_register(struct btd_device *device)
+{
+ struct heartrate_adapter *hr;
+ struct btd_adapter *adapter;
+ struct heartrate_device *hrdev;
+
+ adapter = device_get_adapter(device);
+
+ hr = find_heartrate_adapter(adapter);
+
+ if (hr == NULL)
+ return -1;
+
+ hrdev = g_new0(struct heartrate_device, 1);
+ hrdev->dev = btd_device_ref(device);
+ hrdev->hr = hr;
+
+ hr->devices = g_slist_prepend(hr->devices, hrdev);
+
+ return 0;
+}
+void heartrate_device_unregister(struct btd_device *device)
+{
+ struct heartrate_adapter *hr;
+ struct btd_adapter *adapter;
+ struct heartrate_device *hrdev;
+ GSList *l;
+
+ adapter = device_get_adapter(device);
+
+ hr = find_heartrate_adapter(adapter);
+ if (hr == NULL)
+ return;
+
+ l = g_slist_find_custom(hr->devices, device, cmp_device);
+ if (l == NULL)
+ return;
+
+ hrdev = l->data;
+
+ hr->devices = g_slist_remove(hr->devices, hrdev);
+
+ destroy_heartrate_device(hrdev);
+}
diff --git a/profiles/heartrate/heartrate.h b/profiles/heartrate/heartrate.h
new file mode 100644
index 0000000..486f5b3
--- /dev/null
+++ b/profiles/heartrate/heartrate.h
@@ -0,0 +1,26 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2012 Tieto Poland
+ *
+ * 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 heartrate_adapter_register(struct btd_adapter *adapter);
+void heartrate_adapter_unregister(struct btd_adapter *adapter);
+int heartrate_device_register(struct btd_device *device);
+void heartrate_device_unregister(struct btd_device *device);
diff --git a/profiles/heartrate/main.c b/profiles/heartrate/main.c
new file mode 100644
index 0000000..40f34bc
--- /dev/null
+++ b/profiles/heartrate/main.c
@@ -0,0 +1,52 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2012 Tieto Poland
+ *
+ * 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 <stdint.h>
+#include <glib.h>
+#include <errno.h>
+
+#include "plugin.h"
+#include "manager.h"
+#include "hcid.h"
+#include "log.h"
+
+static int heartrate_init(void)
+{
+ if (!main_opts.gatt_enabled) {
+ DBG("GATT is disabled");
+ return -ENOTSUP;
+ }
+
+ return heartrate_manager_init();
+}
+
+static void heartrate_exit(void)
+{
+ heartrate_manager_exit();
+}
+
+BLUETOOTH_PLUGIN_DEFINE(heartrate, VERSION, BLUETOOTH_PLUGIN_PRIORITY_DEFAULT,
+ heartrate_init, heartrate_exit)
diff --git a/profiles/heartrate/manager.c b/profiles/heartrate/manager.c
new file mode 100644
index 0000000..2a9326d
--- /dev/null
+++ b/profiles/heartrate/manager.c
@@ -0,0 +1,76 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2012 Tieto Poland
+ *
+ * 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
+ *
+ */
+
+#include <gdbus.h>
+#include <errno.h>
+#include <stdbool.h>
+#include <bluetooth/uuid.h>
+
+#include "adapter.h"
+#include "device.h"
+#include "profile.h"
+#include "att.h"
+#include "gattrib.h"
+#include "gatt.h"
+#include "heartrate.h"
+#include "manager.h"
+
+static int heartrate_adapter_probe(struct btd_adapter *adapter)
+{
+ return heartrate_adapter_register(adapter);
+}
+
+static void heartrate_adapter_remove(struct btd_adapter *adapter)
+{
+ heartrate_adapter_unregister(adapter);
+}
+
+static int heartrate_device_probe(struct btd_device *device, GSList *uuids)
+{
+ return heartrate_device_register(device);
+}
+
+static void heartrate_device_remove(struct btd_device *device)
+{
+ heartrate_device_unregister(device);
+}
+
+static struct btd_profile hrp_profile = {
+ .name = "Heart Rate GATT Driver",
+ .remote_uuids = BTD_UUIDS(HEART_RATE_UUID),
+
+ .device_probe = heartrate_device_probe,
+ .device_remove = heartrate_device_remove,
+
+ .adapter_probe = heartrate_adapter_probe,
+ .adapter_remove = heartrate_adapter_remove,
+};
+
+int heartrate_manager_init(void)
+{
+ return btd_profile_register(&hrp_profile);
+}
+
+void heartrate_manager_exit(void)
+{
+ btd_profile_unregister(&hrp_profile);
+}
diff --git a/profiles/heartrate/manager.h b/profiles/heartrate/manager.h
new file mode 100644
index 0000000..de799f6
--- /dev/null
+++ b/profiles/heartrate/manager.h
@@ -0,0 +1,24 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2012 Tieto Poland
+ *
+ * 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 heartrate_manager_init(void);
+void heartrate_manager_exit(void);
--
1.7.11.3
From: Rafal Garbat <[email protected]>
This patch adds support for org.bluez.HeartRateManager interface on adapters
which allows to register and unregister per-adapter watcher.
Change-Id: Id4ba29672efb4985bc99250a3fe9db661f21fcae
---
profiles/heartrate/heartrate.c | 155 ++++++++++++++++++++++++++++++++++++++++-
1 file changed, 154 insertions(+), 1 deletion(-)
diff --git a/profiles/heartrate/heartrate.c b/profiles/heartrate/heartrate.c
index 24b691a..861428a 100644
--- a/profiles/heartrate/heartrate.c
+++ b/profiles/heartrate/heartrate.c
@@ -24,13 +24,16 @@
#include <config.h>
#endif
+#include <gdbus.h>
#include <errno.h>
#include <stdbool.h>
#include <glib.h>
#include <bluetooth/uuid.h>
#include "adapter.h"
+#include "dbus-common.h"
#include "device.h"
+#include "error.h"
#include "gattrib.h"
#include "attio.h"
#include "att.h"
@@ -38,9 +41,12 @@
#include "heartrate.h"
#include "log.h"
+#define HEART_RATE_MANAGER_IFACE "org.bluez.HeartRateManager"
+
struct heartrate_adapter {
struct btd_adapter *adapter;
GSList *devices;
+ GSList *watchers;
};
struct heartrate_device {
@@ -64,6 +70,13 @@ struct descriptor {
bt_uuid_t uuid;
};
+struct watcher {
+ struct heartrate_adapter *hr;
+ guint id;
+ char *srv;
+ char *path;
+};
+
static GSList *heartrate_adapters = NULL;
static gint cmp_adapter(gconstpointer a, gconstpointer b)
@@ -88,6 +101,19 @@ static gint cmp_device(gconstpointer a, gconstpointer b)
return -1;
}
+static gint cmp_watcher(gconstpointer a, gconstpointer b)
+{
+ const struct watcher *watcher = a;
+ const struct watcher *match = b;
+ int ret;
+
+ ret = g_strcmp0(watcher->srv, match->srv);
+ if (ret != 0)
+ return ret;
+
+ return g_strcmp0(watcher->path, match->path);
+}
+
static struct heartrate_adapter *
find_heartrate_adapter(struct btd_adapter *adapter)
{
@@ -108,6 +134,22 @@ static void destroy_char(gpointer user_data)
g_free(c);
}
+static void remove_watcher(gpointer user_data)
+{
+ struct watcher *watcher = user_data;
+
+ g_dbus_remove_watch(get_dbus_connection(), watcher->id);
+}
+
+static void destroy_watcher(gpointer user_data)
+{
+ struct watcher *watcher = user_data;
+
+ g_free(watcher->path);
+ g_free(watcher->srv);
+ g_free(watcher);
+}
+
static void destroy_heartrate_device(gpointer user_data)
{
struct heartrate_device *hrdev = user_data;
@@ -133,9 +175,106 @@ static void destroy_heartrate_adapter(gpointer user_data)
if (hr->devices != NULL)
g_slist_free_full(hr->devices, destroy_heartrate_device);
+ if (hr->watchers != NULL)
+ g_slist_free_full(hr->watchers, remove_watcher);
+
g_free(hr);
}
+static void watcher_exit(DBusConnection *conn, void *user_data)
+{
+ struct watcher *watcher = user_data;
+ struct heartrate_adapter *hr = watcher->hr;
+
+ DBG("Heart Rate watcher %s disconnected", watcher->path);
+
+ hr->watchers = g_slist_remove(hr->watchers, watcher);
+ g_dbus_remove_watch(conn, watcher->id);
+}
+
+static struct watcher *find_watcher(GSList *list, const char *sender,
+ const char *path)
+{
+ struct watcher *match;
+ GSList *l;
+
+ match = g_new0(struct watcher, 1);
+ match->srv = g_strdup(sender);
+ match->path = g_strdup(path);
+
+ l = g_slist_find_custom(list, match, cmp_watcher);
+ destroy_watcher(match);
+
+ if (l != NULL)
+ return l->data;
+
+ return NULL;
+}
+
+static DBusMessage *register_watcher(DBusConnection *conn, DBusMessage *msg,
+ void *data)
+{
+ const char *sender = dbus_message_get_sender(msg);
+ struct heartrate_adapter *hr = data;
+ struct watcher *watcher;
+ char *path;
+
+ if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_OBJECT_PATH, &path,
+ DBUS_TYPE_INVALID))
+ return btd_error_invalid_args(msg);
+
+ watcher = find_watcher(hr->watchers, sender, path);
+ if (watcher != NULL)
+ return btd_error_already_exists(msg);
+
+ DBG("Heart Rate watcher %s registered", path);
+
+ watcher = g_new0(struct watcher, 1);
+ watcher->srv = g_strdup(sender);
+ watcher->path = g_strdup(path);
+ watcher->hr = hr;
+ watcher->id = g_dbus_add_disconnect_watch(conn, sender, watcher_exit,
+ watcher, destroy_watcher);
+
+ hr->watchers = g_slist_prepend(hr->watchers, watcher);
+
+ return dbus_message_new_method_return(msg);
+}
+
+static DBusMessage *unregister_watcher(DBusConnection *conn, DBusMessage *msg,
+ void *data)
+{
+ const char *sender = dbus_message_get_sender(msg);
+ struct heartrate_adapter *hr = data;
+ struct watcher *watcher;
+ char *path;
+
+ if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_OBJECT_PATH, &path,
+ DBUS_TYPE_INVALID))
+ return btd_error_invalid_args(msg);
+
+ watcher = find_watcher(hr->watchers, sender, path);
+ if (watcher == NULL)
+ return btd_error_does_not_exist(msg);
+
+ DBG("Heart Rate watcher %s unregistered", path);
+
+ hr->watchers = g_slist_remove(hr->watchers, watcher);
+ g_dbus_remove_watch(get_dbus_connection(), watcher->id);
+
+ return dbus_message_new_method_return(msg);
+}
+
+static const GDBusMethodTable heartrate_manager_methods[] = {
+ { GDBUS_METHOD("RegisterWatcher",
+ GDBUS_ARGS({ "agent", "o" }), NULL,
+ register_watcher) },
+ { GDBUS_METHOD("UnregisterWatcher",
+ GDBUS_ARGS({ "agent", "o" }), NULL,
+ unregister_watcher) },
+ { }
+};
+
static void process_heartrate_char(struct characteristic *ch)
{
if (g_strcmp0(ch->attr.uuid, HEART_RATE_CONTROL_POINT_UUID) == 0)
@@ -276,6 +415,18 @@ int heartrate_adapter_register(struct btd_adapter *adapter)
hr = g_new0(struct heartrate_adapter, 1);
hr->adapter = adapter;
+ if (!g_dbus_register_interface(get_dbus_connection(),
+ adapter_get_path(adapter),
+ HEART_RATE_MANAGER_IFACE,
+ heartrate_manager_methods,
+ NULL, NULL, hr,
+ destroy_heartrate_adapter)) {
+ error("D-Bus failed to register %s interface",
+ HEART_RATE_MANAGER_IFACE);
+ destroy_heartrate_adapter(hr);
+ return -EIO;
+ }
+
heartrate_adapters = g_slist_prepend(heartrate_adapters, hr);
return 0;
@@ -290,7 +441,9 @@ void heartrate_adapter_unregister(struct btd_adapter *adapter)
heartrate_adapters = g_slist_remove(heartrate_adapters, hr);
- destroy_heartrate_adapter(hr);
+ g_dbus_unregister_interface(get_dbus_connection(),
+ adapter_get_path(hr->adapter),
+ HEART_RATE_MANAGER_IFACE);
}
int heartrate_device_register(struct btd_device *device,
--
1.7.11.3
From: Rafal Garbat <[email protected]>
Change-Id: Iebaa631d03e64389e31899a2c2b23cd555790c3c
---
profiles/heartrate/heartrate.c | 73 +++++++++++++++++++++++++++++++++++++++++-
1 file changed, 72 insertions(+), 1 deletion(-)
diff --git a/profiles/heartrate/heartrate.c b/profiles/heartrate/heartrate.c
index 3d918e9..839d9e3 100644
--- a/profiles/heartrate/heartrate.c
+++ b/profiles/heartrate/heartrate.c
@@ -55,6 +55,13 @@ struct heartrate_device {
struct characteristic {
struct gatt_char attr;
struct heartrate_device *hrdev;
+ GSList *desc;
+};
+
+struct descriptor {
+ struct characteristic *ch;
+ uint16_t handle;
+ bt_uuid_t uuid;
};
static GSList *heartrate_adapters = NULL;
@@ -92,6 +99,15 @@ find_heartrate_adapter(struct btd_adapter *adapter)
return l->data;
}
+static void destroy_char(gpointer user_data)
+{
+ struct characteristic *c = user_data;
+
+ g_slist_free_full(c->desc, g_free);
+
+ g_free(c);
+}
+
static void destroy_heartrate_device(gpointer user_data)
{
struct heartrate_device *hrdev = user_data;
@@ -103,7 +119,7 @@ static void destroy_heartrate_device(gpointer user_data)
g_attrib_unref(hrdev->attrib);
if (hrdev->chars != NULL)
- g_slist_free_full(hrdev->chars, g_free);
+ g_slist_free_full(hrdev->chars, destroy_char);
btd_device_unref(hrdev->dev);
g_free(hrdev->svc_range);
@@ -128,6 +144,44 @@ static void process_heartrate_char(struct characteristic *ch)
DBG("Body Sensor Location supported by client");
}
+static void discover_desc_cb(guint8 status, const guint8 *pdu,
+ guint16 len, gpointer user_data)
+{
+ struct characteristic *ch = user_data;
+ struct att_data_list *list;
+ uint8_t format;
+ int i;
+
+ if (status != 0) {
+ error("Discover all characteristic descriptors failed [%s]: %s",
+ ch->attr.uuid, att_ecode2str(status));
+ return;
+ }
+
+ list = dec_find_info_resp(pdu, len, &format);
+ if (list == NULL)
+ return;
+
+ for (i = 0; i < list->num; i++) {
+ struct descriptor *desc;
+ uint8_t *value;
+
+ value = list->data[i];
+ desc = g_new0(struct descriptor, 1);
+ desc->handle = att_get_u16(value);
+ desc->ch = ch;
+
+ if (format == 0x01)
+ desc->uuid = att_get_uuid16(&value[2]);
+ else
+ desc->uuid = att_get_uuid128(&value[2]);
+
+ ch->desc = g_slist_append(ch->desc, desc);
+ }
+
+ att_data_list_free(list);
+}
+
static void configure_heartrate_cb(GSList *characteristics, guint8 status,
gpointer user_data)
{
@@ -143,6 +197,7 @@ static void configure_heartrate_cb(GSList *characteristics, guint8 status,
for (l = characteristics; l; l = l->next) {
struct gatt_char *c = l->data;
struct characteristic *ch;
+ uint16_t start, end;
ch = g_new0(struct characteristic, 1);
ch->attr.handle = c->handle;
@@ -154,6 +209,22 @@ static void configure_heartrate_cb(GSList *characteristics, guint8 status,
hrdev->chars = g_slist_append(hrdev->chars, ch);
process_heartrate_char(ch);
+
+ start = c->value_handle + 1;
+
+ if (l->next != NULL) {
+ struct gatt_char *c = l->next->data;
+ if (start == c->handle)
+ continue;
+ end = c->handle - 1;
+ } else if (c->value_handle != hrdev->svc_range->end) {
+ end = hrdev->svc_range->end;
+ } else {
+ continue;
+ }
+
+ gatt_find_info(hrdev->attrib, start, end,
+ discover_desc_cb, ch);
}
}
--
1.7.11.3
Change-Id: I9f5d69c1c0d50160e4a7c2c27c92fb159c197fa1
---
profiles/heartrate/heartrate.c | 40 ++++++++++++++++++++++++++++++++++++++++
1 file changed, 40 insertions(+)
diff --git a/profiles/heartrate/heartrate.c b/profiles/heartrate/heartrate.c
index 06cb12a..6b470e5 100644
--- a/profiles/heartrate/heartrate.c
+++ b/profiles/heartrate/heartrate.c
@@ -31,7 +31,12 @@
#include "adapter.h"
#include "device.h"
+#include "gattrib.h"
+#include "attio.h"
+#include "att.h"
+#include "gatt.h"
#include "heartrate.h"
+#include "log.h"
struct heartrate_adapter {
struct btd_adapter *adapter;
@@ -41,6 +46,8 @@ struct heartrate_adapter {
struct heartrate_device {
struct btd_device *dev;
struct heartrate_adapter *hr;
+ GAttrib *attrib;
+ guint attioid;
};
static GSList *heartrate_adapters = NULL;
@@ -82,6 +89,12 @@ static void destroy_heartrate_device(gpointer user_data)
{
struct heartrate_device *hrdev = user_data;
+ if (hrdev->attioid > 0)
+ btd_device_remove_attio_callback(hrdev->dev, hrdev->attioid);
+
+ if (hrdev->attrib != NULL)
+ g_attrib_unref(hrdev->attrib);
+
btd_device_unref(hrdev->dev);
g_free(hrdev);
}
@@ -90,9 +103,31 @@ static void destroy_heartrate_adapter(gpointer user_data)
{
struct heartrate_adapter *hr = user_data;
+ if (hr->devices != NULL)
+ g_slist_free_full(hr->devices, destroy_heartrate_device);
+
g_free(hr);
}
+static void attio_connected_cb(GAttrib *attrib, gpointer user_data)
+{
+ struct heartrate_device *hrdev = user_data;
+
+ DBG("GATT Connected");
+
+ hrdev->attrib = g_attrib_ref(attrib);
+}
+
+static void attio_disconnected_cb(gpointer user_data)
+{
+ struct heartrate_device *hrdev = user_data;
+
+ DBG("GATT Disconnected");
+
+ g_attrib_unref(hrdev->attrib);
+ hrdev->attrib = NULL;
+}
+
int heartrate_adapter_register(struct btd_adapter *adapter)
{
struct heartrate_adapter *hr;
@@ -136,6 +171,11 @@ int heartrate_device_register(struct btd_device *device)
hr->devices = g_slist_prepend(hr->devices, hrdev);
+ hrdev->attioid = btd_device_add_attio_callback(device,
+ attio_connected_cb,
+ attio_disconnected_cb,
+ hrdev);
+
return 0;
}
void heartrate_device_unregister(struct btd_device *device)
--
1.7.11.3
From: Rafal Garbat <[email protected]>
This patch adds stub to process discovered characteristics.
Change-Id: I983c942a33668e86fa73453a7649295bc0c032b6
---
lib/uuid.h | 2 ++
profiles/heartrate/heartrate.c | 10 ++++++++++
2 files changed, 12 insertions(+)
diff --git a/lib/uuid.h b/lib/uuid.h
index 3488e66..0a9db51 100644
--- a/lib/uuid.h
+++ b/lib/uuid.h
@@ -64,6 +64,8 @@ extern "C" {
#define SAP_UUID "0000112D-0000-1000-8000-00805f9b34fb"
#define HEART_RATE_UUID "0000180d-0000-1000-8000-00805f9b34fb"
+#define HEART_RATE_CONTROL_POINT_UUID "00002a39-0000-1000-8000-00805f9b34fb"
+#define BODY_SENSOR_LOCATION_UUID "00002a38-0000-1000-8000-00805f9b34fb"
#define HEALTH_THERMOMETER_UUID "00001809-0000-1000-8000-00805f9b34fb"
#define TEMPERATURE_MEASUREMENT_UUID "00002a1c-0000-1000-8000-00805f9b34fb"
diff --git a/profiles/heartrate/heartrate.c b/profiles/heartrate/heartrate.c
index 140a2fb..3d918e9 100644
--- a/profiles/heartrate/heartrate.c
+++ b/profiles/heartrate/heartrate.c
@@ -120,6 +120,14 @@ static void destroy_heartrate_adapter(gpointer user_data)
g_free(hr);
}
+static void process_heartrate_char(struct characteristic *ch)
+{
+ if (g_strcmp0(ch->attr.uuid, HEART_RATE_CONTROL_POINT_UUID) == 0)
+ DBG("Heart Rate Control Point supported by client");
+ else if (g_strcmp0(ch->attr.uuid, BODY_SENSOR_LOCATION_UUID) == 0)
+ DBG("Body Sensor Location supported by client");
+}
+
static void configure_heartrate_cb(GSList *characteristics, guint8 status,
gpointer user_data)
{
@@ -144,6 +152,8 @@ static void configure_heartrate_cb(GSList *characteristics, guint8 status,
ch->hrdev = hrdev;
hrdev->chars = g_slist_append(hrdev->chars, ch);
+
+ process_heartrate_char(ch);
}
}
--
1.7.11.3
From: Rafal Garbat <[email protected]>
Change-Id: I56d9fd06aeb9a37451f31257380e0f6d0ed409f2
---
profiles/heartrate/heartrate.c | 31 +++++++++++++++++++++++++++++++
1 file changed, 31 insertions(+)
diff --git a/profiles/heartrate/heartrate.c b/profiles/heartrate/heartrate.c
index 8fec7e7..0e9c80f 100644
--- a/profiles/heartrate/heartrate.c
+++ b/profiles/heartrate/heartrate.c
@@ -433,6 +433,34 @@ static DBusMessage *unregister_watcher(DBusConnection *conn, DBusMessage *msg,
return dbus_message_new_method_return(msg);
}
+
+static DBusMessage *get_properties(DBusConnection *conn, DBusMessage *msg,
+ void *data)
+{
+ struct heartrate_device *hrdev = data;
+ DBusMessageIter iter;
+ DBusMessageIter dict;
+ DBusMessage *reply;
+
+ reply = dbus_message_new_method_return(msg);
+ if (reply == NULL)
+ 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);
+
+ dict_append_entry(&dict, "ResetSupported", DBUS_TYPE_BOOLEAN,
+ &hrdev->has_cp_reset);
+
+ dbus_message_iter_close_container(&iter, &dict);
+
+ return reply;
+}
+
static gint process_att_reset(struct heartrate_device *hrdev)
{
struct characteristic *ch;
@@ -481,6 +509,9 @@ static const GDBusMethodTable heartrate_manager_methods[] = {
};
static const GDBusMethodTable heartrate_device_methods[] = {
+ { GDBUS_METHOD("GetProperties",
+ NULL, GDBUS_ARGS({ "properties", "a{sv}" }),
+ get_properties) },
{ GDBUS_METHOD("Reset", NULL, NULL,
control_point_reset) },
{ }
--
1.7.11.3
From: Rafal Garbat <[email protected]>
This patch reads and stores Body Sensor Location characteristics value.
Change-Id: Ia20235206869479e478bab2705d43e9a694d554a
---
profiles/heartrate/heartrate.c | 42 +++++++++++++++++++++++++++++++++++++++---
1 file changed, 39 insertions(+), 3 deletions(-)
diff --git a/profiles/heartrate/heartrate.c b/profiles/heartrate/heartrate.c
index 97d3c17..aaadee1 100644
--- a/profiles/heartrate/heartrate.c
+++ b/profiles/heartrate/heartrate.c
@@ -43,6 +43,8 @@
#define HEART_RATE_MANAGER_IFACE "org.bluez.HeartRateManager"
+#define SENSOR_LOCATION_SIZE 1
+
struct heartrate_adapter {
struct btd_adapter *adapter;
GSList *devices;
@@ -56,6 +58,8 @@ struct heartrate_device {
guint attioid;
struct att_range *svc_range;
GSList *chars;
+ gboolean has_location;
+ uint8_t location;
};
struct characteristic {
@@ -386,12 +390,44 @@ static const GDBusMethodTable heartrate_manager_methods[] = {
{ }
};
+static void read_sensor_location_cb(guint8 status, const guint8 *pdu,
+ guint16 len, gpointer user_data)
+{
+ struct characteristic *ch = user_data;
+ struct heartrate_device *hrdev = ch->hrdev;
+ uint8_t value[SENSOR_LOCATION_SIZE];
+ ssize_t vlen;
+
+ if (status != 0) {
+ error("Body Sensor Location value read failed: %s",
+ att_ecode2str(status));
+ return;
+ }
+
+ vlen = dec_read_resp(pdu, len, value, sizeof(value));
+ if (vlen < 0) {
+ error("Protocol error.");
+ return;
+ }
+
+ if (vlen != 1) {
+ error("Invalid length for Body Sensor Location");
+ return;
+ }
+
+ hrdev->has_location = TRUE;
+ hrdev->location = value[0];
+}
+
static void process_heartrate_char(struct characteristic *ch)
{
- if (g_strcmp0(ch->attr.uuid, HEART_RATE_CONTROL_POINT_UUID) == 0)
- DBG("Heart Rate Control Point supported by client");
- else if (g_strcmp0(ch->attr.uuid, BODY_SENSOR_LOCATION_UUID) == 0)
+ if (g_strcmp0(ch->attr.uuid, HEART_RATE_CONTROL_POINT_UUID) == 0) {
+ DBG("Heart Rate Control Point reset supported by client");
+ } else if (g_strcmp0(ch->attr.uuid, BODY_SENSOR_LOCATION_UUID) == 0) {
DBG("Body Sensor Location supported by client");
+ gatt_read_char(ch->hrdev->attrib, ch->attr.value_handle, 0,
+ read_sensor_location_cb, ch);
+ }
}
static void process_heartrate_desc(struct descriptor *desc)
--
1.7.11.3
This patch reads and stores Heart Rate Control Point characteristics value.
Change-Id: I2716410c3c03ecb006a484006384aa53630aa6bf
---
profiles/heartrate/heartrate.c | 2 ++
1 file changed, 2 insertions(+)
diff --git a/profiles/heartrate/heartrate.c b/profiles/heartrate/heartrate.c
index aaadee1..a0e0113 100644
--- a/profiles/heartrate/heartrate.c
+++ b/profiles/heartrate/heartrate.c
@@ -60,6 +60,7 @@ struct heartrate_device {
GSList *chars;
gboolean has_location;
uint8_t location;
+ gboolean has_cp_reset;
};
struct characteristic {
@@ -423,6 +424,7 @@ static void process_heartrate_char(struct characteristic *ch)
{
if (g_strcmp0(ch->attr.uuid, HEART_RATE_CONTROL_POINT_UUID) == 0) {
DBG("Heart Rate Control Point reset supported by client");
+ ch->hrdev->has_cp_reset = TRUE;
} else if (g_strcmp0(ch->attr.uuid, BODY_SENSOR_LOCATION_UUID) == 0) {
DBG("Body Sensor Location supported by client");
gatt_read_char(ch->hrdev->attrib, ch->attr.value_handle, 0,
--
1.7.11.3
From: Rafal Garbat <[email protected]>
Change-Id: I638b2f255ab15cce87fa05db14f4a2b4642f7422
---
Makefile.tools | 4 +--
test/test-heartrate | 101 ++++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 103 insertions(+), 2 deletions(-)
create mode 100755 test/test-heartrate
diff --git a/Makefile.tools b/Makefile.tools
index d3b6f57..f59a7c4 100644
--- a/Makefile.tools
+++ b/Makefile.tools
@@ -209,7 +209,7 @@ EXTRA_DIST += test/sap_client.py test/hsplay test/hsmicro \
test/test-network test/simple-agent test/simple-service \
test/simple-endpoint test/test-audio test/test-input \
test/test-sap-server test/test-oob test/test-attrib \
- test/test-proximity test/test-thermometer test/test-health \
- test/test-health-sink test/service-record.dtd \
+ test/test-proximity test/test-thermometer test/test-heartrate \
+ test/test-health test/test-health-sink test/service-record.dtd \
test/service-did.xml test/service-spp.xml test/service-opp.xml \
test/service-ftp.xml test/simple-player test/test-nap
diff --git a/test/test-heartrate b/test/test-heartrate
new file mode 100755
index 0000000..a124f2c
--- /dev/null
+++ b/test/test-heartrate
@@ -0,0 +1,101 @@
+#!/usr/bin/python
+
+from __future__ import absolute_import, print_function, unicode_literals
+
+'''
+Heart Rate Monitor test script
+'''
+
+import gobject
+
+import sys
+import dbus
+import dbus.service
+import dbus.mainloop.glib
+from optparse import OptionParser, make_option
+
+class Watcher(dbus.service.Object):
+ @dbus.service.method("org.bluez.HeartRateWatcher",
+ in_signature="oa{sv}", out_signature="")
+ def MeasurementReceived(self, device, measure):
+ print("Measurement received from %s" % device)
+ print("Value: ", measure["Value"])
+
+ if "Energy" in measure:
+ print("Energy: ", measure["Energy"])
+
+ if "Contact" in measure:
+ print("Contact: ", measure["Contact"])
+
+ if "Location" in measure:
+ print("Location: ", measure["Location"])
+
+ if "Interval" in measure:
+ for i in measure["Interval"]:
+ print("Interval: ", i)
+
+if __name__ == "__main__":
+ dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
+
+ bus = dbus.SystemBus()
+
+ manager = dbus.Interface(bus.get_object("org.bluez", "/"),
+ "org.bluez.Manager")
+
+ option_list = [
+ make_option("-i", "--adapter", action="store",
+ type="string", dest="adapter"),
+ make_option("-b", "--device", action="store",
+ type="string", dest="address"),
+ ]
+
+ parser = OptionParser(option_list=option_list)
+
+ (options, args) = parser.parse_args()
+
+ if not options.address:
+ print("Usage: %s [-i <adapter>] -b <bdaddr> [cmd]" % (sys.argv[0]))
+ print("Possible commands:")
+ print("\tReset")
+ sys.exit(1)
+
+ if options.adapter:
+ adapter_path = manager.FindAdapter(options.adapter)
+ else:
+ adapter_path = manager.DefaultAdapter()
+
+ adapter = dbus.Interface(bus.get_object("org.bluez", adapter_path),
+ "org.bluez.Adapter")
+
+ heartrateManager = dbus.Interface(bus.get_object("org.bluez",
+ adapter_path), "org.bluez.HeartRateManager")
+
+ path = "/test/watcher"
+ heartrateManager.RegisterWatcher(path)
+
+ device_path = adapter.FindDevice(options.address)
+
+ device = dbus.Interface(bus.get_object("org.bluez", device_path),
+ "org.bluez.Device")
+
+ heartrate = dbus.Interface(bus.get_object("org.bluez",
+ device_path), "org.bluez.HeartRate")
+
+ watcher = Watcher(bus, path)
+
+ properties = heartrate.GetProperties()
+ reset_sup = properties["ResetSupported"]
+
+ if len(args) > 0:
+ if args[0] == "Reset":
+ if reset_sup:
+ heartrate.Reset()
+ else:
+ print("Reset not supported")
+ sys.exit(1)
+ else:
+ print("unknown command")
+ sys.exit(1)
+
+ mainloop = gobject.MainLoop()
+ mainloop.run()
--
1.7.11.3
This patch adds Reset method on HeartRate interface to reset Energy Expended.
Change-Id: I54eb89c757627757dc1b934c103fc9444326b36a
---
profiles/heartrate/heartrate.c | 58 +++++++++++++++++++++++++++++++++++++++++-
1 file changed, 57 insertions(+), 1 deletion(-)
diff --git a/profiles/heartrate/heartrate.c b/profiles/heartrate/heartrate.c
index 0031e4d..8fec7e7 100644
--- a/profiles/heartrate/heartrate.c
+++ b/profiles/heartrate/heartrate.c
@@ -43,6 +43,7 @@
#define HEART_RATE_MANAGER_IFACE "org.bluez.HeartRateManager"
#define HEART_RATE_WATCHER_IFACE "org.bluez.HeartRateWatcher"
+#define HEART_RATE_IFACE "org.bluez.HeartRate"
#define MIN_NOTIFICATION_LEN 3 /* 1-byte opcode + 2-byte handle */
@@ -432,6 +433,43 @@ static DBusMessage *unregister_watcher(DBusConnection *conn, DBusMessage *msg,
return dbus_message_new_method_return(msg);
}
+static gint process_att_reset(struct heartrate_device *hrdev)
+{
+ struct characteristic *ch;
+ char *msg;
+ uint8_t atval;
+
+ if (hrdev->attrib == NULL)
+ return -1;
+
+ ch = get_characteristic(hrdev, HEART_RATE_CONTROL_POINT_UUID);
+ if (ch == NULL)
+ return -1;
+
+ atval = 0x01;
+ msg = g_strdup("Reset Control Point");
+
+ gatt_write_char(hrdev->attrib, ch->attr.value_handle, &atval,
+ 1, char_write_cb, msg);
+ return 0;
+}
+
+static DBusMessage *control_point_reset(DBusConnection *conn, DBusMessage *msg,
+ void *data)
+{
+ struct heartrate_device *hrdev = data;
+
+ if (!hrdev->has_cp_reset)
+ return btd_error_not_supported(msg);
+
+ if (process_att_reset(hrdev) < 0)
+ return btd_error_not_available(msg);
+
+ DBG("Energy Expended Value has been reset");
+
+ return dbus_message_new_method_return(msg);
+}
+
static const GDBusMethodTable heartrate_manager_methods[] = {
{ GDBUS_METHOD("RegisterWatcher",
GDBUS_ARGS({ "agent", "o" }), NULL,
@@ -442,6 +480,12 @@ static const GDBusMethodTable heartrate_manager_methods[] = {
{ }
};
+static const GDBusMethodTable heartrate_device_methods[] = {
+ { GDBUS_METHOD("Reset", NULL, NULL,
+ control_point_reset) },
+ { }
+};
+
static void read_sensor_location_cb(guint8 status, const guint8 *pdu,
guint16 len, gpointer user_data)
{
@@ -844,6 +888,16 @@ int heartrate_device_register(struct btd_device *device,
hrdev->dev = btd_device_ref(device);
hrdev->hr = hr;
+ if (!g_dbus_register_interface(get_dbus_connection(),
+ device_get_path(device), HEART_RATE_IFACE,
+ heartrate_device_methods, NULL, NULL,
+ hrdev, destroy_heartrate_device)) {
+ error("D-Bus failed to register %s interface",
+ HEART_RATE_IFACE);
+ destroy_heartrate_device(hrdev);
+ return -EIO;
+ }
+
hr->devices = g_slist_prepend(hr->devices, hrdev);
hrdev->svc_range = g_new0(struct att_range, 1);
@@ -878,5 +932,7 @@ void heartrate_device_unregister(struct btd_device *device)
hr->devices = g_slist_remove(hr->devices, hrdev);
- destroy_heartrate_device(hrdev);
+ g_dbus_unregister_interface(get_dbus_connection(),
+ device_get_path(device),
+ HEART_RATE_IFACE);
}
--
1.7.11.3
Change-Id: Ic64336c084891ad63b67fe900663a7835c1f78fb
---
profiles/heartrate/heartrate.c | 193 +++++++++++++++++++++++++++++++++++++++++
1 file changed, 193 insertions(+)
diff --git a/profiles/heartrate/heartrate.c b/profiles/heartrate/heartrate.c
index dec09d0..0031e4d 100644
--- a/profiles/heartrate/heartrate.c
+++ b/profiles/heartrate/heartrate.c
@@ -42,11 +42,18 @@
#include "log.h"
#define HEART_RATE_MANAGER_IFACE "org.bluez.HeartRateManager"
+#define HEART_RATE_WATCHER_IFACE "org.bluez.HeartRateWatcher"
#define MIN_NOTIFICATION_LEN 3 /* 1-byte opcode + 2-byte handle */
#define SENSOR_LOCATION_SIZE 1
+#define HR_VALUE_FORMAT 0x01
+#define SENSOR_CONTACT_SUPPORT 0x02
+#define SENSOR_CONTACT_DETECTED 0x04
+#define ENERGY_EXP_STATUS 0x08
+#define RR_INTERVAL 0x10
+
struct heartrate_adapter {
struct btd_adapter *adapter;
GSList *devices;
@@ -78,6 +85,18 @@ struct descriptor {
bt_uuid_t uuid;
};
+struct measurement {
+ struct heartrate_device *hrdev;
+ uint16_t value;
+ gboolean has_energy;
+ uint16_t energy;
+ gboolean has_contact;
+ gboolean contact;
+ uint16_t num_interval;
+ uint16_t *interval;
+ char *location;
+};
+
struct watcher {
struct heartrate_adapter *hr;
guint id;
@@ -85,6 +104,25 @@ struct watcher {
char *path;
};
+static const char *location_type[] = {
+ "Other",
+ "Chest",
+ "Wrist",
+ "Finger",
+ "Hand",
+ "Earlobe",
+ "Foot"
+};
+
+static const gchar *location2str(uint8_t value)
+{
+ if (value < G_N_ELEMENTS(location_type))
+ return location_type[value];
+
+ error("Location type %d reserved for future use", value);
+ return NULL;
+}
+
static GSList *heartrate_adapters = NULL;
static gint cmp_adapter(gconstpointer a, gconstpointer b)
@@ -125,6 +163,14 @@ static gint cmp_descriptor(gconstpointer a, gconstpointer b)
return bt_uuid_cmp(&desc->uuid, uuid);
}
+static gint cmp_char_val_handle(gconstpointer a, gconstpointer b)
+{
+ const struct characteristic *ch = a;
+ const uint16_t *handle = b;
+
+ return ch->attr.value_handle - *handle;
+}
+
static gint cmp_watcher(gconstpointer a, gconstpointer b)
{
const struct watcher *watcher = a;
@@ -556,12 +602,159 @@ static void configure_heartrate_cb(GSList *characteristics, guint8 status,
}
}
+static void update_watcher(gpointer data, gpointer user_data)
+{
+ struct watcher *w = data;
+ struct measurement *m = user_data;
+ struct heartrate_device *hrdev = m->hrdev;
+ const gchar *path = device_get_path(hrdev->dev);
+ DBusMessageIter iter;
+ DBusMessageIter dict;
+ DBusMessage *msg;
+
+ msg = dbus_message_new_method_call(w->srv, w->path,
+ HEART_RATE_WATCHER_IFACE,
+ "MeasurementReceived");
+ if (msg == NULL)
+ return;
+
+ dbus_message_iter_init_append(msg, &iter);
+
+ dbus_message_iter_append_basic(&iter, DBUS_TYPE_OBJECT_PATH , &path);
+
+ 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);
+
+ dict_append_entry(&dict, "Value", DBUS_TYPE_UINT16, &m->value);
+ if (m->has_energy)
+ dict_append_entry(&dict, "Energy", DBUS_TYPE_UINT16, &m->energy);
+ if (m->has_contact)
+ dict_append_entry(&dict, "Contact", DBUS_TYPE_BOOLEAN, &m->contact);
+ dict_append_entry(&dict, "Location", DBUS_TYPE_STRING, &m->location);
+
+ if (m->num_interval > 0)
+ dict_append_array(&dict, "Interval", DBUS_TYPE_UINT16,
+ &m->interval, m->num_interval);
+
+ dbus_message_iter_close_container(&iter, &dict);
+
+ dbus_message_set_no_reply(msg, TRUE);
+ g_dbus_send_message(get_dbus_connection(), msg);
+}
+
+static void recv_measurement(struct heartrate_device *hrdev,
+ struct measurement *m)
+{
+ GSList *wlist = hrdev->hr->watchers;
+
+ m->hrdev = hrdev;
+
+ g_slist_foreach(wlist, update_watcher, m);
+}
+
+static void proc_measurement(struct heartrate_device *hrdev,
+ const uint8_t *pdu, uint16_t len)
+{
+ struct measurement m;
+ uint8_t flags;
+
+ if (len < 4) {
+ error("Mandatory flags are not provided");
+ return;
+ }
+
+ flags = pdu[3];
+
+ pdu += 4;
+ len -= 4;
+
+ memset(&m, 0, sizeof(m));
+
+ if (flags & HR_VALUE_FORMAT) {
+ if (len < 2) {
+ error("Heart Rate Measurement field missing");
+ return;
+ }
+
+ m.value = att_get_u16(pdu);
+ pdu += 2;
+ len -= 2;
+ } else {
+ if (len < 1) {
+ error("Heart Rate Measurement field missing");
+ return;
+ }
+
+ m.value = *pdu;
+ pdu++;
+ len--;
+ }
+
+ if (flags & ENERGY_EXP_STATUS) {
+ if (len < 2) {
+ error("Energy Expended field missing");
+ return;
+ }
+
+ m.has_energy = TRUE;
+ m.energy = att_get_u16(pdu);
+ pdu += 2;
+ len -= 2;
+ }
+
+ if (flags & RR_INTERVAL) {
+ int i;
+
+ if (len == 0 || (len % 2 != 0)) {
+ error("RR-Interval field malformed");
+ return;
+ }
+
+ m.num_interval = len / 2;
+ m.interval = g_new(uint16_t, m.num_interval);
+
+ for (i = 0; i < m.num_interval; pdu += 2, i++)
+ m.interval[i] = att_get_u16(pdu);
+ }
+
+ if (flags & SENSOR_CONTACT_SUPPORT) {
+ m.has_contact = TRUE;
+ m.contact = !!(flags & SENSOR_CONTACT_DETECTED);
+ }
+
+ if (hrdev->has_location)
+ m.location = g_strdup(location2str(hrdev->location));
+
+ recv_measurement(hrdev, &m);
+
+ g_free(m.interval);
+ g_free(m.location);
+}
+
static void notify_handler(const uint8_t *pdu, uint16_t len, gpointer user_data)
{
+ struct heartrate_device *hrdev = user_data;
+ const struct characteristic *ch;
+ uint16_t handle;
+ GSList *l;
+
if (len < MIN_NOTIFICATION_LEN) {
error("Bad pdu received");
return;
}
+
+ handle = att_get_u16(&pdu[1]);
+ l = g_slist_find_custom(hrdev->chars, &handle, cmp_char_val_handle);
+ if (l == NULL) {
+ error("Unexpected handle: 0x%04x", handle);
+ return;
+ }
+
+ ch = l->data;
+ if (g_strcmp0(ch->attr.uuid, HEART_RATE_MEASUREMENT_UUID) == 0)
+ proc_measurement(hrdev, pdu, len);
}
static void attio_connected_cb(GAttrib *attrib, gpointer user_data)
--
1.7.11.3
This patch will enable measurement notification when first watcher is
registered or when device is connected and watcher is already registered.
Measurement will be disabled when last watcher is unregistered.
Change-Id: I1490ee9a76cd17201a3612f597f1fa12f85a6cbb
---
profiles/heartrate/heartrate.c | 130 ++++++++++++++++++++++++++++++++++++++++-
1 file changed, 129 insertions(+), 1 deletion(-)
diff --git a/profiles/heartrate/heartrate.c b/profiles/heartrate/heartrate.c
index 861428a..97d3c17 100644
--- a/profiles/heartrate/heartrate.c
+++ b/profiles/heartrate/heartrate.c
@@ -101,6 +101,22 @@ static gint cmp_device(gconstpointer a, gconstpointer b)
return -1;
}
+static gint cmp_char_uuid(gconstpointer a, gconstpointer b)
+{
+ const struct characteristic *ch = a;
+ const char *uuid = b;
+
+ return g_strcmp0(ch->attr.uuid, uuid);
+}
+
+static gint cmp_descriptor(gconstpointer a, gconstpointer b)
+{
+ const struct descriptor *desc = a;
+ const bt_uuid_t *uuid = b;
+
+ return bt_uuid_cmp(&desc->uuid, uuid);
+}
+
static gint cmp_watcher(gconstpointer a, gconstpointer b)
{
const struct watcher *watcher = a;
@@ -181,6 +197,92 @@ static void destroy_heartrate_adapter(gpointer user_data)
g_free(hr);
}
+static struct characteristic *
+get_characteristic(struct heartrate_device *hrdev, const char *uuid)
+{
+ GSList *l;
+
+ l = g_slist_find_custom(hrdev->chars, uuid, cmp_char_uuid);
+ if (l == NULL)
+ return NULL;
+
+ return l->data;
+}
+
+static struct descriptor *get_descriptor(struct characteristic *ch,
+ const bt_uuid_t *uuid)
+{
+ GSList *l;
+
+ l = g_slist_find_custom(ch->desc, uuid, cmp_descriptor);
+ if (l == NULL)
+ return NULL;
+
+ return l->data;
+}
+
+static void char_write_cb(guint8 status, const guint8 *pdu, guint16 len,
+ gpointer user_data)
+{
+ char *msg = user_data;
+
+ if (status != 0)
+ error("%s failed", msg);
+
+ g_free(msg);
+}
+
+static void measurement_toggle(struct heartrate_device *hrdev, gboolean enable)
+{
+ struct characteristic *ch;
+ struct descriptor *desc;
+ bt_uuid_t btuuid;
+ uint8_t atval[2];
+ char *msg;
+
+ if (hrdev->attrib == NULL)
+ return;
+
+ ch = get_characteristic(hrdev, HEART_RATE_MEASUREMENT_UUID);
+ if (ch == NULL) {
+ DBG("Heart Rate Measurement characteristic not found");
+ return;
+ }
+
+ bt_uuid16_create(&btuuid, GATT_CLIENT_CHARAC_CFG_UUID);
+ desc = get_descriptor(ch, &btuuid);
+ if (desc == NULL) {
+ DBG("Client Characteristic Configuration descriptor not found");
+ return;
+ }
+
+ if (enable) {
+ att_put_u16(GATT_CLIENT_CHARAC_CFG_NOTIF_BIT, atval);
+ msg = g_strdup("Enable measurement");
+ } else {
+ att_put_u16(0x0000, atval);
+ msg = g_strdup("Disable measurement");
+ }
+
+ gatt_write_char(hrdev->attrib, desc->handle, atval,
+ 2, char_write_cb, msg);
+
+}
+
+static void measurement_enable(gpointer data, gpointer user_data)
+{
+ struct heartrate_device *hrdev = data;
+
+ measurement_toggle(hrdev, TRUE);
+}
+
+static void measurement_disable(gpointer data, gpointer user_data)
+{
+ struct heartrate_device *hrdev = data;
+
+ measurement_toggle(hrdev, FALSE);
+}
+
static void watcher_exit(DBusConnection *conn, void *user_data)
{
struct watcher *watcher = user_data;
@@ -190,6 +292,9 @@ static void watcher_exit(DBusConnection *conn, void *user_data)
hr->watchers = g_slist_remove(hr->watchers, watcher);
g_dbus_remove_watch(conn, watcher->id);
+
+ if (g_slist_length(hr->watchers) == 0)
+ g_slist_foreach(hr->devices, measurement_disable, 0);
}
static struct watcher *find_watcher(GSList *list, const char *sender,
@@ -236,6 +341,9 @@ static DBusMessage *register_watcher(DBusConnection *conn, DBusMessage *msg,
watcher->id = g_dbus_add_disconnect_watch(conn, sender, watcher_exit,
watcher, destroy_watcher);
+ if (g_slist_length(hr->watchers) == 0)
+ g_slist_foreach(hr->devices, measurement_enable, 0);
+
hr->watchers = g_slist_prepend(hr->watchers, watcher);
return dbus_message_new_method_return(msg);
@@ -262,6 +370,9 @@ static DBusMessage *unregister_watcher(DBusConnection *conn, DBusMessage *msg,
hr->watchers = g_slist_remove(hr->watchers, watcher);
g_dbus_remove_watch(get_dbus_connection(), watcher->id);
+ if (g_slist_length(hr->watchers) == 0)
+ g_slist_foreach(hr->devices, measurement_disable, 0);
+
return dbus_message_new_method_return(msg);
}
@@ -291,9 +402,26 @@ static void process_heartrate_desc(struct descriptor *desc)
bt_uuid16_create(&btuuid, GATT_CLIENT_CHARAC_CFG_UUID);
- if (bt_uuid_cmp(&desc->uuid, &btuuid) == 0)
+ if (bt_uuid_cmp(&desc->uuid, &btuuid) == 0) {
+ uint8_t atval[2];
+ char *msg;
+
+ if (g_strcmp0(ch->attr.uuid, HEART_RATE_MEASUREMENT_UUID) != 0)
+ goto done;
+
+ if (g_slist_length(ch->hrdev->hr->watchers) == 0)
+ return;
+
+ att_put_u16(GATT_CLIENT_CHARAC_CFG_NOTIF_BIT, atval);
+ msg = g_strdup("Enable measurement");
+
+ gatt_write_char(ch->hrdev->attrib, desc->handle,
+ atval, 2, char_write_cb, msg);
+
return;
+ }
+done:
bt_uuid_to_string(&desc->uuid, uuidstr, MAX_LEN_UUID_STR);
DBG("Ignored descriptor %s in characteristic %s", uuidstr,
ch->attr.uuid);
--
1.7.11.3
From: Rafal Garbat <[email protected]>
Change-Id: Iaf2416fbcfb1901ff962f5571f5ebeea0ebf6cc9
---
src/bluetooth.conf | 1 +
1 file changed, 1 insertion(+)
diff --git a/src/bluetooth.conf b/src/bluetooth.conf
index 664dbd9..77a9371 100644
--- a/src/bluetooth.conf
+++ b/src/bluetooth.conf
@@ -16,6 +16,7 @@
<allow send_interface="org.bluez.MediaPlayer"/>
<allow send_interface="org.bluez.Watcher"/>
<allow send_interface="org.bluez.ThermometerWatcher"/>
+ <allow send_interface="org.bluez.HeartRateWatcher"/>
</policy>
<policy at_console="true">
--
1.7.11.3
This patch adds stub for handling characteristics value changed notification.
Change-Id: Ic975d16327cfa32361fa590195b1d2b58fe5da03
---
profiles/heartrate/heartrate.c | 23 ++++++++++++++++++++++-
1 file changed, 22 insertions(+), 1 deletion(-)
diff --git a/profiles/heartrate/heartrate.c b/profiles/heartrate/heartrate.c
index a0e0113..dec09d0 100644
--- a/profiles/heartrate/heartrate.c
+++ b/profiles/heartrate/heartrate.c
@@ -43,6 +43,8 @@
#define HEART_RATE_MANAGER_IFACE "org.bluez.HeartRateManager"
+#define MIN_NOTIFICATION_LEN 3 /* 1-byte opcode + 2-byte handle */
+
#define SENSOR_LOCATION_SIZE 1
struct heartrate_adapter {
@@ -56,6 +58,7 @@ struct heartrate_device {
struct heartrate_adapter *hr;
GAttrib *attrib;
guint attioid;
+ guint attionotid;
struct att_range *svc_range;
GSList *chars;
gboolean has_location;
@@ -178,8 +181,10 @@ static void destroy_heartrate_device(gpointer user_data)
if (hrdev->attioid > 0)
btd_device_remove_attio_callback(hrdev->dev, hrdev->attioid);
- if (hrdev->attrib != NULL)
+ if (hrdev->attrib != NULL) {
+ g_attrib_unregister(hrdev->attrib, hrdev->attionotid);
g_attrib_unref(hrdev->attrib);
+ }
if (hrdev->chars != NULL)
g_slist_free_full(hrdev->chars, destroy_char);
@@ -551,6 +556,14 @@ static void configure_heartrate_cb(GSList *characteristics, guint8 status,
}
}
+static void notify_handler(const uint8_t *pdu, uint16_t len, gpointer user_data)
+{
+ if (len < MIN_NOTIFICATION_LEN) {
+ error("Bad pdu received");
+ return;
+ }
+}
+
static void attio_connected_cb(GAttrib *attrib, gpointer user_data)
{
struct heartrate_device *hrdev = user_data;
@@ -559,6 +572,9 @@ static void attio_connected_cb(GAttrib *attrib, gpointer user_data)
hrdev->attrib = g_attrib_ref(attrib);
+ hrdev->attionotid = g_attrib_register(hrdev->attrib,
+ ATT_OP_HANDLE_NOTIFY, notify_handler, hrdev, NULL);
+
gatt_discover_char(hrdev->attrib, hrdev->svc_range->start,
hrdev->svc_range->end, NULL,
configure_heartrate_cb, hrdev);
@@ -570,6 +586,11 @@ static void attio_disconnected_cb(gpointer user_data)
DBG("GATT Disconnected");
+ if (hrdev->attionotid > 0) {
+ g_attrib_unregister(hrdev->attrib, hrdev->attionotid);
+ hrdev->attionotid = 0;
+ }
+
g_attrib_unref(hrdev->attrib);
hrdev->attrib = NULL;
}
--
1.7.11.3
From: Rafal Garbat <[email protected]>
This patch adds stub to process discovered characteristics descriptors.
Change-Id: I0d881e0a8924af85677e83f8593131ad66f3efe6
---
lib/uuid.h | 1 +
profiles/heartrate/heartrate.c | 18 ++++++++++++++++++
2 files changed, 19 insertions(+)
diff --git a/lib/uuid.h b/lib/uuid.h
index 0a9db51..15a80a0 100644
--- a/lib/uuid.h
+++ b/lib/uuid.h
@@ -64,6 +64,7 @@ extern "C" {
#define SAP_UUID "0000112D-0000-1000-8000-00805f9b34fb"
#define HEART_RATE_UUID "0000180d-0000-1000-8000-00805f9b34fb"
+#define HEART_RATE_MEASUREMENT_UUID "00002a37-0000-1000-8000-00805f9b34fb"
#define HEART_RATE_CONTROL_POINT_UUID "00002a39-0000-1000-8000-00805f9b34fb"
#define BODY_SENSOR_LOCATION_UUID "00002a38-0000-1000-8000-00805f9b34fb"
diff --git a/profiles/heartrate/heartrate.c b/profiles/heartrate/heartrate.c
index 839d9e3..24b691a 100644
--- a/profiles/heartrate/heartrate.c
+++ b/profiles/heartrate/heartrate.c
@@ -144,6 +144,22 @@ static void process_heartrate_char(struct characteristic *ch)
DBG("Body Sensor Location supported by client");
}
+static void process_heartrate_desc(struct descriptor *desc)
+{
+ struct characteristic *ch = desc->ch;
+ char uuidstr[MAX_LEN_UUID_STR];
+ bt_uuid_t btuuid;
+
+ bt_uuid16_create(&btuuid, GATT_CLIENT_CHARAC_CFG_UUID);
+
+ if (bt_uuid_cmp(&desc->uuid, &btuuid) == 0)
+ return;
+
+ bt_uuid_to_string(&desc->uuid, uuidstr, MAX_LEN_UUID_STR);
+ DBG("Ignored descriptor %s in characteristic %s", uuidstr,
+ ch->attr.uuid);
+}
+
static void discover_desc_cb(guint8 status, const guint8 *pdu,
guint16 len, gpointer user_data)
{
@@ -177,6 +193,8 @@ static void discover_desc_cb(guint8 status, const guint8 *pdu,
desc->uuid = att_get_uuid128(&value[2]);
ch->desc = g_slist_append(ch->desc, desc);
+
+ process_heartrate_desc(desc);
}
att_data_list_free(list);
--
1.7.11.3
From: Rafal Garbat <[email protected]>
This patch adds support to discover Heart Rate Service characteristics when
connected to remote device.
Change-Id: Ibbae71a35b3d9c295106e12041d51c54e6928f08
---
profiles/heartrate/heartrate.c | 49 +++++++++++++++++++++++++++++++++++++++++-
profiles/heartrate/heartrate.h | 3 ++-
profiles/heartrate/manager.c | 22 ++++++++++++++++++-
3 files changed, 71 insertions(+), 3 deletions(-)
diff --git a/profiles/heartrate/heartrate.c b/profiles/heartrate/heartrate.c
index 6b470e5..140a2fb 100644
--- a/profiles/heartrate/heartrate.c
+++ b/profiles/heartrate/heartrate.c
@@ -48,6 +48,13 @@ struct heartrate_device {
struct heartrate_adapter *hr;
GAttrib *attrib;
guint attioid;
+ struct att_range *svc_range;
+ GSList *chars;
+};
+
+struct characteristic {
+ struct gatt_char attr;
+ struct heartrate_device *hrdev;
};
static GSList *heartrate_adapters = NULL;
@@ -95,7 +102,11 @@ static void destroy_heartrate_device(gpointer user_data)
if (hrdev->attrib != NULL)
g_attrib_unref(hrdev->attrib);
+ if (hrdev->chars != NULL)
+ g_slist_free_full(hrdev->chars, g_free);
+
btd_device_unref(hrdev->dev);
+ g_free(hrdev->svc_range);
g_free(hrdev);
}
@@ -109,6 +120,33 @@ static void destroy_heartrate_adapter(gpointer user_data)
g_free(hr);
}
+static void configure_heartrate_cb(GSList *characteristics, guint8 status,
+ gpointer user_data)
+{
+ struct heartrate_device *hrdev = user_data;
+ GSList *l;
+
+ if (status != 0) {
+ error("Discover Heart Rate characteristics: %s",
+ att_ecode2str(status));
+ return;
+ }
+
+ for (l = characteristics; l; l = l->next) {
+ struct gatt_char *c = l->data;
+ struct characteristic *ch;
+
+ ch = g_new0(struct characteristic, 1);
+ ch->attr.handle = c->handle;
+ ch->attr.properties = c->properties;
+ ch->attr.value_handle = c->value_handle;
+ memcpy(ch->attr.uuid, c->uuid, MAX_LEN_UUID_STR + 1);
+ ch->hrdev = hrdev;
+
+ hrdev->chars = g_slist_append(hrdev->chars, ch);
+ }
+}
+
static void attio_connected_cb(GAttrib *attrib, gpointer user_data)
{
struct heartrate_device *hrdev = user_data;
@@ -116,6 +154,10 @@ static void attio_connected_cb(GAttrib *attrib, gpointer user_data)
DBG("GATT Connected");
hrdev->attrib = g_attrib_ref(attrib);
+
+ gatt_discover_char(hrdev->attrib, hrdev->svc_range->start,
+ hrdev->svc_range->end, NULL,
+ configure_heartrate_cb, hrdev);
}
static void attio_disconnected_cb(gpointer user_data)
@@ -152,7 +194,8 @@ void heartrate_adapter_unregister(struct btd_adapter *adapter)
destroy_heartrate_adapter(hr);
}
-int heartrate_device_register(struct btd_device *device)
+int heartrate_device_register(struct btd_device *device,
+ struct gatt_primary *pattr)
{
struct heartrate_adapter *hr;
struct btd_adapter *adapter;
@@ -171,6 +214,10 @@ int heartrate_device_register(struct btd_device *device)
hr->devices = g_slist_prepend(hr->devices, hrdev);
+ hrdev->svc_range = g_new0(struct att_range, 1);
+ hrdev->svc_range->start = pattr->range.start;
+ hrdev->svc_range->end = pattr->range.end;
+
hrdev->attioid = btd_device_add_attio_callback(device,
attio_connected_cb,
attio_disconnected_cb,
diff --git a/profiles/heartrate/heartrate.h b/profiles/heartrate/heartrate.h
index 486f5b3..ec3ed7a 100644
--- a/profiles/heartrate/heartrate.h
+++ b/profiles/heartrate/heartrate.h
@@ -22,5 +22,6 @@
int heartrate_adapter_register(struct btd_adapter *adapter);
void heartrate_adapter_unregister(struct btd_adapter *adapter);
-int heartrate_device_register(struct btd_device *device);
+int heartrate_device_register(struct btd_device *device,
+ struct gatt_primary *pattr);
void heartrate_device_unregister(struct btd_device *device);
diff --git a/profiles/heartrate/manager.c b/profiles/heartrate/manager.c
index 2a9326d..d7d7ae4 100644
--- a/profiles/heartrate/manager.c
+++ b/profiles/heartrate/manager.c
@@ -34,6 +34,14 @@
#include "heartrate.h"
#include "manager.h"
+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 heartrate_adapter_probe(struct btd_adapter *adapter)
{
return heartrate_adapter_register(adapter);
@@ -46,7 +54,19 @@ static void heartrate_adapter_remove(struct btd_adapter *adapter)
static int heartrate_device_probe(struct btd_device *device, GSList *uuids)
{
- return heartrate_device_register(device);
+ struct gatt_primary *pattr;
+ GSList *primaries, *l;
+
+ primaries = btd_device_get_primaries(device);
+
+ l = g_slist_find_custom(primaries, HEART_RATE_UUID,
+ primary_uuid_cmp);
+ if (l == NULL)
+ return -EINVAL;
+
+ pattr = l->data;
+
+ return heartrate_device_register(device, pattr);
}
static void heartrate_device_remove(struct btd_device *device)
--
1.7.11.3
From: Santiago Carot-Nemesio <[email protected]>
Change-Id: I83c03b861832274f3e647439b356f4ffc1a6f523
---
doc/heartrate-api.txt | 80 +++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 80 insertions(+)
create mode 100644 doc/heartrate-api.txt
diff --git a/doc/heartrate-api.txt b/doc/heartrate-api.txt
new file mode 100644
index 0000000..074681c
--- /dev/null
+++ b/doc/heartrate-api.txt
@@ -0,0 +1,80 @@
+ Heart Rate API description
+****************************************
+
+Copyright (C) 2012 Santiago Carot-Nemesio <[email protected]>
+Copyright (C) 2012 Tieto Poland
+
+Heart Rate Manager hierarchy
+============================
+
+Service org.bluez
+Interface org.bluez.HeartRateManager
+Object path [variable prefix]/{hci0,hci1,...}
+
+Methods RegisterWatcher(object agent)
+
+ Registers a watcher to monitor heart rate measurements.
+
+ Possible Errors: org.bluez.Error.InvalidArguments
+
+ UnregisterWatcher(object agent)
+
+ Unregisters a watcher.
+
+Heart Rate Profile hierarchy
+============================
+
+Service org.bluez
+Interface org.bluez.HeartRate
+Object path [variable prefix]/{hci0,hci1,...}/dev_XX_XX_XX_XX_XX_XX
+
+Methods dict GetProperties()
+
+ Returns all properties for the interface. See the
+ Properties section for the available properties.
+
+ Reset()
+
+ Restart the accumulation of energy expended from zero.
+
+ Possible Errors: org.bluez.Error.NotSupported
+
+Properties boolean ResetSupported [readonly]
+
+ True if energy expended is supported.
+
+Heart Rate Watcher hierarchy
+
+============================
+Service unique name
+Interface org.bluez.HeartRateWatcher
+Object path freely definable
+
+Methods void MeasurementReceived(object device, dict measure)
+
+ This callback is called whenever a heart rate
+ measurement is received from the heart rate device.
+ The unit for the Value is expressed in beats per
+ minute (bpm). The energy field is optional and
+ represents the accumulated energy expended in
+ kilo Joules since last time it was reset. Furthermore,
+ the device will be automatically reset when it
+ is needed.
+ The Contact field, if present, indicates
+ that the device supports contact sensor, besides it
+ will be true if skin contact is detected. The optional
+ interval field is an array containing RR-Interval
+ values which represent the time between two R-Wave
+ detections, where the RR-Interval value 0 is older
+ than the value 1 and so on.
+
+ Dict is defined as below:
+ {
+ "Value" : uint16,
+ "Energy" : uint16,
+ "Contact" : boolean,
+ "Location" : ("Other", "Chest", "Wrist","Finger",
+ "Hand", "Earlobe", "Foot"),
+ "Interval" : array{uint16}
+ }
+
--
1.7.11.3