2011-11-12 00:20:05

by Vinicius Costa Gomes

[permalink] [raw]
Subject: [RFC BlueZ 0/4] Enabling notifications using the Generic Attribute API

Hi,

These are just a few patches that I have around that helped tests here.

In the last BlueZ meeting, a aproach similar to this was discussed and
seemed that the right aproach was to enable notifications/indications
for services automatically when they support it. This email is more
for re-starting the discussion.

Cheers,
--

Vinicius Costa Gomes (4):
Add a way to manually configure the CCC attribute of a Characteristic
Add a way to enable notifications using the CCC descriptor
Update test-attrib to handle notifications and indications
Fix responding indications and notifications with not supported

attrib/client.c | 131 ++++++++++++++++++++++++++++++++++++++++++++++++-
doc/attribute-api.txt | 7 +++
src/attrib-server.c | 4 ++
test/test-attrib | 45 ++++++++++++++++-
4 files changed, 184 insertions(+), 3 deletions(-)

--
1.7.7



2011-11-12 00:20:09

by Vinicius Costa Gomes

[permalink] [raw]
Subject: [RFC BlueZ 4/4] Fix responding indications and notifications with not supported

---
src/attrib-server.c | 4 ++++
1 files changed, 4 insertions(+), 0 deletions(-)

diff --git a/src/attrib-server.c b/src/attrib-server.c
index e412061..2abec7f 100644
--- a/src/attrib-server.c
+++ b/src/attrib-server.c
@@ -904,6 +904,10 @@ static void channel_handler(const uint8_t *ipdu, uint16_t len,
break;
case ATT_OP_HANDLE_CNF:
return;
+ case ATT_OP_HANDLE_IND:
+ case ATT_OP_HANDLE_NOTIFY:
+ /* The attribute client is already handling these */
+ return;
case ATT_OP_READ_MULTI_REQ:
case ATT_OP_PREP_WRITE_REQ:
case ATT_OP_EXEC_WRITE_REQ:
--
1.7.7


2011-11-12 00:20:08

by Vinicius Costa Gomes

[permalink] [raw]
Subject: [RFC BlueZ 3/4] Update test-attrib to handle notifications and indications

For supporting indications and notification we need two features,
a way to configure the CCC attribute (implemented recently) and
a way to listen for notifications, that was already implemented
using the Characteristic Watcher.
---
test/test-attrib | 45 +++++++++++++++++++++++++++++++++++++++++++--
1 files changed, 43 insertions(+), 2 deletions(-)

diff --git a/test/test-attrib b/test/test-attrib
index b9e83c5..f5787e1 100755
--- a/test/test-attrib
+++ b/test/test-attrib
@@ -9,9 +9,18 @@ import gobject

import sys
import dbus
+import dbus.service
import dbus.mainloop.glib
from optparse import OptionParser, make_option

+class Watcher(dbus.service.Object):
+ exit_on_release = True
+
+ @dbus.service.method("org.bluez.Watcher",
+ in_signature="oay", out_signature="")
+ def ValueChanged(self, characteristic, value):
+ print "ValueChanged: %s -> %s" % (characteristic, value)
+
dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
bus = dbus.SystemBus()
mainloop = gobject.MainLoop()
@@ -34,6 +43,9 @@ else:
adapter = dbus.Interface(bus.get_object("org.bluez", adapter_path),
"org.bluez.Adapter")

+watcher_path = "/test/watcher"
+watcher = Watcher(bus, watcher_path);
+
if (len(args) < 1):
print "Usage: %s <command>" % (sys.argv[0])
print ""
@@ -41,6 +53,8 @@ if (len(args) < 1):
print " services <address>"
print " discover <service path>"
print " chars <service path>"
+ print " register <service path>"
+ print " config <char path> [\"notify\", \"indicate\" or \"none\"]"
sys.exit(1)

if (args[0] == "list"):
@@ -98,11 +112,38 @@ if (args[0] == "chars"):
char = dbus.Interface(bus.get_object("org.bluez", path),
"org.bluez.Characteristic")
charprop = char.GetProperties()
- print " Name: %s" % charprop["Name"]
- print " UUID: %s" % charprop["UUID"]
+ for (k, v) in charprop.items():
+ print "%s: %s" % (k, v);
print
print
sys.exit(0)

+if (args[0] == "register"):
+ if (len(args) < 2):
+ print "Need service path parameter"
+ sys.exit(0)
+
+ service = dbus.Interface(bus.get_object("org.bluez", args[1]),
+ "org.bluez.Characteristic")
+
+ service.RegisterCharacteristicsWatcher(dbus.ObjectPath(watcher_path))
+ mainloop.run()
+
+if (args[0] == "config"):
+ if (len(args) < 2):
+ print "Need characteristic path parameter"
+ sys.exit(0)
+
+ char = dbus.Interface(bus.get_object("org.bluez", args[1]),
+ "org.bluez.Characteristic")
+ if (len(args) == 2):
+ charprop = char.GetProperties()
+ print "config %s" % (charprop["Config"])
+ else:
+ char.SetProperty("Config", args[2])
+ charprop = char.GetProperties()
+ print "config %s" % (charprop["Config"])
+ sys.exit(0)
+
print "Unknown command"
sys.exit(1)
--
1.7.7


2011-11-12 00:20:07

by Vinicius Costa Gomes

[permalink] [raw]
Subject: [RFC BlueZ 2/4] Add a way to enable notifications using the CCC descriptor

This implements the property "Config" of a Characterisitc, that if
present indicates that the Characteristics supports this descriptor.
Setting this property sends a write command with the correct value.
---
attrib/client.c | 131 ++++++++++++++++++++++++++++++++++++++++++++++++++++++-
1 files changed, 130 insertions(+), 1 deletions(-)

diff --git a/attrib/client.c b/attrib/client.c
index bfe5877..a0e8b12 100644
--- a/attrib/client.c
+++ b/attrib/client.c
@@ -78,6 +78,7 @@ struct gatt_service {
char *path;
GSList *chars;
GSList *offline_chars;
+ GSList *offline_configs;
GSList *watchers;
struct query *query;
};
@@ -91,6 +92,8 @@ struct characteristic {
char type[MAX_LEN_UUID_STR + 1];
char *name;
char *desc;
+ uint16_t ccc;
+ uint16_t ccc_hnd;
struct format *format;
uint8_t *value;
size_t vlen;
@@ -137,6 +140,7 @@ static void gatt_service_free(struct gatt_service *gatt)
g_slist_free_full(gatt->watchers, watcher_free);
g_slist_free_full(gatt->chars, characteristic_free);
g_slist_free(gatt->offline_chars);
+ g_slist_free(gatt->offline_configs);
g_free(gatt->path);
btd_device_unref(gatt->dev);
dbus_connection_unref(gatt->conn);
@@ -175,6 +179,18 @@ static int watcher_cmp(gconstpointer a, gconstpointer b)
return g_strcmp0(watcher->path, match->path);
}

+static const char *config2str(uint16_t ccc)
+{
+ switch (ccc) {
+ case 0x01:
+ return "notify";
+ case 0x02:
+ return "indicate";
+ default:
+ return "none";
+ }
+}
+
static void append_char_dict(DBusMessageIter *iter, struct characteristic *chr)
{
DBusMessageIter dict;
@@ -201,6 +217,11 @@ static void append_char_dict(DBusMessageIter *iter, struct characteristic *chr)
dict_append_array(&dict, "Value", DBUS_TYPE_BYTE, &chr->value,
chr->vlen);

+ if (chr->ccc_hnd) {
+ const char *config = config2str(chr->ccc);
+ dict_append_entry(&dict, "Config", DBUS_TYPE_STRING, &config);
+ }
+
/* FIXME: Missing Format, Value and Representation */

dbus_message_iter_close_container(iter, &dict);
@@ -300,7 +321,26 @@ static void offline_char_written(gpointer user_data)

gatt->offline_chars = g_slist_remove(gatt->offline_chars, chr);

- if (gatt->offline_chars || gatt->watchers)
+ if (gatt->offline_chars || gatt->watchers || gatt->offline_configs)
+ return;
+
+ btd_device_remove_attio_callback(gatt->dev, gatt->attioid);
+ gatt->attioid = 0;
+}
+
+static void config_written(guint8 status, const guint8 *pdu,
+ guint16 len, gpointer user_data)
+{
+ struct characteristic *chr = user_data;
+ struct gatt_service *gatt = chr->gatt;
+ const char *str = config2str(chr->ccc);
+
+ gatt->offline_configs = g_slist_remove(gatt->offline_configs, chr);
+
+ emit_property_changed(gatt->conn, chr->path, CHAR_INTERFACE,
+ "Config", DBUS_TYPE_STRING, &str);
+
+ if (gatt->offline_chars || gatt->watchers || gatt->offline_configs)
return;

btd_device_remove_attio_callback(gatt->dev, gatt->attioid);
@@ -316,6 +356,18 @@ static void offline_char_write(gpointer data, gpointer user_data)
offline_char_written, chr);
}

+static void offline_config_write(gpointer data, gpointer user_data)
+{
+ struct characteristic *chr = data;
+ GAttrib *attrib = user_data;
+ uint8_t buf[2];
+
+ att_put_u16(chr->ccc, buf);
+
+ gatt_write_char(attrib, chr->ccc_hnd, buf, sizeof(buf),
+ config_written, chr);
+}
+
static void attio_connected(GAttrib *attrib, gpointer user_data)
{
struct gatt_service *gatt = user_data;
@@ -328,6 +380,7 @@ static void attio_connected(GAttrib *attrib, gpointer user_data)
events_handler, gatt, NULL);

g_slist_foreach(gatt->offline_chars, offline_char_write, attrib);
+ g_slist_foreach(gatt->offline_configs, offline_config_write, attrib);
}

static void attio_disconnected(gpointer user_data)
@@ -401,6 +454,47 @@ static DBusMessage *unregister_watcher(DBusConnection *conn,
return dbus_message_new_method_return(msg);
}

+static DBusMessage *set_config_char(DBusConnection *conn, DBusMessage *msg,
+ DBusMessageIter *iter, struct characteristic *chr)
+{
+ struct gatt_service *gatt = chr->gatt;
+ const char *str;
+ uint8_t buf[2];
+
+ if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_STRING)
+ return btd_error_invalid_args(msg);
+
+ dbus_message_iter_get_basic(iter, &str);
+
+ if (chr->ccc_hnd == 0)
+ return btd_error_not_supported(msg);
+
+ if (g_str_equal(str, "notify"))
+ chr->ccc = 0x01;
+ else if (g_str_equal(str, "indicate"))
+ chr->ccc = 0x02;
+ else if (g_str_equal(str, "none"))
+ chr->ccc = 0;
+ else
+ return btd_error_invalid_args(msg);
+
+ if (gatt->attioid == 0) {
+ gatt->attioid = btd_device_add_attio_callback(gatt->dev,
+ attio_connected,
+ attio_disconnected,
+ gatt);
+ gatt->offline_configs = g_slist_append(gatt->offline_configs, chr);
+
+ return dbus_message_new_method_return(msg);
+ }
+
+ att_put_u16(chr->ccc, buf);
+ gatt_write_char(gatt->attrib, chr->ccc_hnd, buf, sizeof(buf),
+ config_written, chr);
+
+ return NULL;
+}
+
static DBusMessage *set_value(DBusConnection *conn, DBusMessage *msg,
DBusMessageIter *iter, struct characteristic *chr)
{
@@ -475,6 +569,9 @@ static DBusMessage *set_property(DBusConnection *conn,
if (g_str_equal("Value", property))
return set_value(conn, msg, &sub, chr);

+ if (g_str_equal("Config", property))
+ return set_config_char(conn, msg, &sub, chr);
+
return btd_error_invalid_args(msg);
}

@@ -720,6 +817,33 @@ static void update_char_value(guint8 status, const guint8 *pdu,
g_free(current);
}

+
+static void update_char_config(guint8 status, const guint8 *pdu, guint16 len,
+ gpointer user_data)
+{
+ struct query_data *current = user_data;
+ struct gatt_service *gatt = current->gatt;
+ struct characteristic *chr = current->chr;
+ uint8_t value[2];
+
+ if (status != 0)
+ goto done;
+
+ if (len < 3)
+ goto done;
+
+ chr->ccc = att_get_u16(&pdu[1]);
+ chr->ccc_hnd = current->handle;
+ memcpy(value, &chr->ccc, sizeof(chr->ccc));
+
+ store_attribute(gatt, current->handle, GATT_CLIENT_CHARAC_CFG_UUID,
+ value, sizeof(value));
+
+done:
+ query_list_remove(gatt, current);
+ g_free(current);
+}
+
static int uuid_desc16_cmp(bt_uuid_t *uuid, guint16 desc)
{
bt_uuid_t u16;
@@ -777,6 +901,11 @@ static void descriptor_cb(guint8 status, const guint8 *pdu, guint16 plen,
query_list_append(gatt, qfmt);
gatt_read_char(gatt->attrib, handle, 0,
update_char_format, qfmt);
+ } else if (uuid_desc16_cmp(&uuid,
+ GATT_CLIENT_CHARAC_CFG_UUID) == 0) {
+ query_list_append(gatt, qfmt);
+ gatt_read_char(gatt->attrib, handle, 0,
+ update_char_config, qfmt);
} else
g_free(qfmt);
}
--
1.7.7


2011-11-12 00:20:06

by Vinicius Costa Gomes

[permalink] [raw]
Subject: [RFC BlueZ 1/4] Add a way to manually configure the CCC attribute of a Characteristic

---
doc/attribute-api.txt | 7 +++++++
1 files changed, 7 insertions(+), 0 deletions(-)

diff --git a/doc/attribute-api.txt b/doc/attribute-api.txt
index 98d7f30..9f2273f 100644
--- a/doc/attribute-api.txt
+++ b/doc/attribute-api.txt
@@ -151,6 +151,13 @@ Properties string UUID [readonly]
Friendly representation of the Characteristic Value
based on the format attribute.

+ string Config [readwrite]
+
+ Configuration of the characteristic, valid values:
+ "notify", "indicate" or "none". This property is only
+ present if the CCC Descriptor could be found during
+ the discovery of characteristics.
+

Characteristic Watcher hierarchy
===============================
--
1.7.7