This is a new set of patches to enhance thermometer plugin fuctionality.
These patches enable watchers to receiave final/intermediate measurements
from thermometers devices.
[PATCH 1/6] Manage GATT attribute indications in handle callback.
[PATCH 2/6] Parse final measurement indication
[PATCH 3/6] Add org.bluez.ThermometerWatcher interface to default
[PATCH 4/6] Implement EnableIntermediateMeasurement D-Bus method
[PATCH 5/6] Implement DisableIntermediateMeasurement D-Bus method
[PATCH 6/6] Notify intermediate measurements
---
thermometer/thermometer.c | 50 +++++++++++++++++++++++++++++++++++++++-----
1 files changed, 44 insertions(+), 6 deletions(-)
diff --git a/thermometer/thermometer.c b/thermometer/thermometer.c
index 849b040..384f883 100644
--- a/thermometer/thermometer.c
+++ b/thermometer/thermometer.c
@@ -61,6 +61,7 @@ struct thermometer {
struct att_range *svc_range; /* Thermometer range */
guint attioid; /* Att watcher id */
guint attindid; /* Att incications id */
+ guint attnotid; /* Att notifications id */
GSList *chars; /* Characteristics */
GSList *fwatchers; /* Final measurements */
GSList *iwatchers; /* Intermediate measurements */
@@ -152,6 +153,9 @@ static void destroy_thermometer(gpointer user_data)
if (t->attindid > 0)
g_attrib_unregister(t->attrib, t->attindid);
+ if (t->attnotid > 0)
+ g_attrib_unregister(t->attrib, t->attnotid);
+
if (t->attrib != NULL)
g_attrib_unref(t->attrib);
@@ -894,14 +898,14 @@ static void update_watcher(struct watcher *w, struct measurement *m)
static void recv_measurement(struct thermometer *t, struct measurement *msmt)
{
- GSList *l;
+ GSList *l, *ll;
- if (g_strcmp0(msmt->msmnt, "Intermediate") == 0) {
- DBG("Notification of intermediate measurement not implemented");
- return;
- }
+ if (g_strcmp0(msmt->msmnt, "Intermediate") == 0)
+ ll = t->iwatchers;
+ else
+ ll = t->fwatchers;
- for (l = t->fwatchers; l; l = l->next)
+ for (l = ll; l; l = l->next)
update_watcher(l->data, msmt);
}
@@ -1022,6 +1026,33 @@ done:
NULL);
}
+static void notif_handler(const uint8_t *pdu, uint16_t len, gpointer user_data)
+{
+ struct thermometer *t = user_data;
+ const struct characteristic *ch;
+ uint16_t handle;
+ GSList *l;
+
+ if (len < 3) {
+ DBG("Bad pdu received");
+ return;
+ }
+
+ if (pdu[0] != ATT_OP_HANDLE_NOTIFY) {
+ DBG("Not notification received");
+ return;
+ }
+
+ handle = att_get_u16(&pdu[1]);
+ l = g_slist_find_custom(t->chars, &handle, cmp_char_val_handle);
+ if (l == NULL)
+ return;
+
+ ch = l->data;
+ if (g_strcmp0(ch->attr.uuid, INTERMEDIATE_TEMPERATURE_UUID) == 0)
+ proc_measurement(t, pdu, len, FALSE);
+}
+
static void attio_connected_cb(GAttrib *attrib, gpointer user_data)
{
struct thermometer *t = user_data;
@@ -1030,6 +1061,8 @@ static void attio_connected_cb(GAttrib *attrib, gpointer user_data)
t->attindid = g_attrib_register(t->attrib, ATT_OP_HANDLE_IND,
ind_handler, t, NULL);
+ t->attnotid = g_attrib_register(t->attrib, ATT_OP_HANDLE_NOTIFY,
+ notif_handler, t, NULL);
gatt_discover_char(t->attrib, t->svc_range->start, t->svc_range->end,
NULL, configure_thermometer_cb, t);
}
@@ -1045,6 +1078,11 @@ static void attio_disconnected_cb(gpointer user_data)
t->attindid = 0;
}
+ if (t->attnotid > 0) {
+ g_attrib_unregister(t->attrib, t->attnotid);
+ t->attnotid = 0;
+ }
+
g_attrib_unref(t->attrib);
t->attrib = NULL;
}
--
1.7.7.2
---
thermometer/thermometer.c | 154 ++++++++++++++++++++++++++++++++++++++++++++-
1 files changed, 153 insertions(+), 1 deletions(-)
diff --git a/thermometer/thermometer.c b/thermometer/thermometer.c
index 0928f15..3a82e20 100644
--- a/thermometer/thermometer.c
+++ b/thermometer/thermometer.c
@@ -47,6 +47,13 @@
#define INTERMEDIATE_TEMPERATURE_UUID "00002a1e-0000-1000-8000-00805f9b34fb"
#define MEASUREMENT_INTERVAL_UUID "00002a21-0000-1000-8000-00805f9b34fb"
+/* Temperature measurement flag fields */
+#define TEMP_UNITS 0x01
+#define TEMP_TIME_STAMP 0x02
+#define TEMP_TYPE 0x04
+
+#define FLOAT_MAX_MANTISSA 16777216 /* 2^24 */
+
struct thermometer {
DBusConnection *conn; /* The connection to the bus */
struct btd_device *dev; /* Device reference */
@@ -84,8 +91,36 @@ struct watcher {
gchar *path;
};
+struct measurement {
+ gint16 exp;
+ gint32 mant;
+ guint64 time;
+ gboolean suptime;
+ gchar *unit;
+ gchar *type;
+ gchar *msmnt;
+};
+
static GSList *thermometers = NULL;
+static gchar *temptype2str(uint8_t value)
+{
+ switch (value) {
+ case 1: return "Armpit";
+ case 2: return "Body";
+ case 3: return "Ear";
+ case 4: return "Finger";
+ case 5: return "Intestines";
+ case 6: return "Mouth";
+ case 7: return "Rectum";
+ case 8: return "Toe";
+ case 9: return "Tympanum";
+ default:
+ error("Temperature type %d reserved for future use", value);
+ return NULL;
+ };
+}
+
static void destroy_watcher(gpointer user_data)
{
struct watcher *watcher = user_data;
@@ -714,10 +749,127 @@ static GDBusSignalTable thermometer_signals[] = {
{ }
};
+static void update_watcher(struct watcher *w, struct measurement *m)
+{
+ DBusConnection *conn = w->t->conn;
+ DBusMessageIter iter;
+ DBusMessageIter dict;
+ DBusMessage *msg;
+
+ msg = dbus_message_new_method_call(w->srv, w->path,
+ "org.bluez.ThermometerWatcher",
+ "MeasurementReceived");
+ if (msg == NULL)
+ return;
+
+ dbus_message_iter_init_append(msg, &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, "Exponent", DBUS_TYPE_INT16, &m->exp);
+ dict_append_entry(&dict, "Mantissa", DBUS_TYPE_INT32, &m->mant);
+ dict_append_entry(&dict, "Unit", DBUS_TYPE_STRING, &m->unit);
+
+ if (m->suptime)
+ dict_append_entry(&dict, "Time", DBUS_TYPE_UINT64, &m->time);
+
+ dict_append_entry(&dict, "Type", DBUS_TYPE_STRING, &m->type);
+ dict_append_entry(&dict, "Measurement", DBUS_TYPE_STRING, &m->msmnt);
+
+ dbus_message_iter_close_container(&iter, &dict);
+
+ dbus_message_set_no_reply(msg, TRUE);
+ g_dbus_send_message(conn, msg);
+}
+
+static void recv_measurement(struct thermometer *t, struct measurement *msmt)
+{
+ GSList *l;
+
+ if (g_strcmp0(msmt->msmnt, "Intermediate") == 0) {
+ DBG("Notification of intermediate measurement not implemented");
+ return;
+ }
+
+ for (l = t->fwatchers; l; l = l->next)
+ update_watcher(l->data, msmt);
+}
+
static void proc_measurement(struct thermometer *t, const uint8_t *pdu,
uint16_t len, gboolean final)
{
- DBG("TODO: Process measurement indication");
+ struct measurement msmt;
+ uint8_t flags;
+ uint32_t raw;
+
+ if (len < 4) {
+ DBG("Mandatory flags are not provided");
+ return;
+ }
+
+ flags = pdu[3];
+ if (flags & TEMP_UNITS)
+ msmt.unit = "Fahrenheit";
+ else
+ msmt.unit = "Celsius";
+
+ if (len < 8) {
+ DBG("Temperature measurement value is not provided");
+ return;
+ }
+
+ raw = att_get_u32(&pdu[4]);
+ msmt.mant = raw & 0x00FFFFFF;
+ msmt.exp = ((gint32) raw) >> 24;
+
+ if (msmt.mant & 0x00800000) {
+ /* convert to C2 negative value */
+ msmt.mant = msmt.mant - FLOAT_MAX_MANTISSA;
+ }
+
+ if (flags & TEMP_TIME_STAMP) {
+ struct tm ts;
+ time_t time;
+
+ if (len < 15) {
+ DBG("Can't get time stamp value");
+ return;
+ }
+
+ ts.tm_year = att_get_u16(&pdu[8]) - 1900;
+ ts.tm_mon = pdu[10];
+ ts.tm_mday = pdu[11];
+ ts.tm_hour = pdu[12];
+ ts.tm_min = pdu[13];
+ ts.tm_sec = pdu[14];
+ ts.tm_isdst = -1;
+
+ time = mktime(&ts);
+ msmt.time = (guint64) time;
+ msmt.suptime = TRUE;
+ } else
+ msmt.suptime = FALSE;
+
+ if (flags & TEMP_TYPE) {
+ if (len < 16) {
+ DBG("Can't get temperature type");
+ return;
+ }
+
+ msmt.type = temptype2str(pdu[15]);
+ } else if (t->has_type)
+ msmt.type = temptype2str(t->type);
+ else {
+ DBG("Can't get temperature type");
+ return;
+ }
+
+ msmt.msmnt = final ? "Final" : "Intermediate";
+
+ recv_measurement(t, &msmt);
}
static void proc_measurement_interval(struct thermometer *t, const uint8_t *pdu,
--
1.7.7.2
---
thermometer/thermometer.c | 63 ++++++++++++++++++++++++++++++++++++++++++--
1 files changed, 60 insertions(+), 3 deletions(-)
diff --git a/thermometer/thermometer.c b/thermometer/thermometer.c
index 2472f1e..849b040 100644
--- a/thermometer/thermometer.c
+++ b/thermometer/thermometer.c
@@ -650,6 +650,44 @@ static void disable_final_measurement(struct thermometer *t)
gatt_write_char(t->attrib, desc->handle, atval, 2, measurement_cb, msg);
}
+static void disable_intermediate_measurement(struct thermometer *t)
+{
+ struct characteristic *ch;
+ struct descriptor *desc;
+ bt_uuid_t btuuid;
+ uint8_t atval[2];
+ gchar *msg;
+
+ ch = get_characteristic(t, INTERMEDIATE_TEMPERATURE_UUID);
+ if (ch == NULL) {
+ DBG("Intermediate 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;
+ }
+
+ atval[0] = 0x00;
+ atval[1] = 0x00;
+ msg = g_strdup("Disable intermediate measurement");
+ gatt_write_char(t->attrib, desc->handle, atval, 2, measurement_cb, msg);
+}
+
+static void remove_int_watcher(struct thermometer *t, struct watcher *w)
+{
+ if (!g_slist_find(t->iwatchers, w))
+ return;
+
+ t->iwatchers = g_slist_remove(t->iwatchers, w);
+
+ if (g_slist_length(t->iwatchers) == 0)
+ disable_intermediate_measurement(t);
+}
+
static void watcher_exit(DBusConnection *conn, void *user_data)
{
struct watcher *watcher = user_data;
@@ -657,6 +695,8 @@ static void watcher_exit(DBusConnection *conn, void *user_data)
DBG("Thermometer watcher %s disconnected", watcher->path);
+ remove_int_watcher(t, watcher);
+
t->fwatchers = g_slist_remove(t->fwatchers, watcher);
watcher->id = 0;
@@ -734,6 +774,8 @@ static DBusMessage *unregister_watcher(DBusConnection *conn, DBusMessage *msg,
DBG("Thermometer watcher %s unregistered", path);
+ remove_int_watcher(t, watcher);
+
t->fwatchers = g_slist_remove(t->fwatchers, watcher);
destroy_watcher(watcher);
@@ -778,9 +820,24 @@ static DBusMessage *enable_intermediate(DBusConnection *conn, DBusMessage *msg,
static DBusMessage *disable_intermediate(DBusConnection *conn, DBusMessage *msg,
void *data)
{
- /* TODO: */
- return g_dbus_create_error(msg, ERROR_INTERFACE ".ThermometerError",
- "Function not implemented.");
+ const gchar *sender = dbus_message_get_sender(msg);
+ struct thermometer *t = data;
+ struct watcher *watcher;
+ gchar *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(t->iwatchers, sender, path);
+ if (watcher == NULL)
+ return btd_error_does_not_exist(msg);
+
+ DBG("Intermediate measurement %s unregistered", path);
+
+ remove_int_watcher(t, watcher);
+
+ return dbus_message_new_method_return(msg);
}
static GDBusMethodTable thermometer_methods[] = {
--
1.7.7.2
---
thermometer/thermometer.c | 74 +++++++++++++++++++++++++++++++++++++-------
1 files changed, 62 insertions(+), 12 deletions(-)
diff --git a/thermometer/thermometer.c b/thermometer/thermometer.c
index 3a82e20..2472f1e 100644
--- a/thermometer/thermometer.c
+++ b/thermometer/thermometer.c
@@ -63,6 +63,7 @@ struct thermometer {
guint attindid; /* Att incications id */
GSList *chars; /* Characteristics */
GSList *fwatchers; /* Final measurements */
+ GSList *iwatchers; /* Intermediate measurements */
gboolean intermediate;
guint8 type;
guint16 interval;
@@ -557,7 +558,7 @@ static struct descriptor *get_descriptor(struct characteristic *ch,
return l->data;
}
-static void final_measurement_cb(guint8 status, const guint8 *pdu,
+static void measurement_cb(guint8 status, const guint8 *pdu,
guint16 len, gpointer user_data)
{
gchar *msg = user_data;
@@ -592,8 +593,34 @@ static void enable_final_measurement(struct thermometer *t)
atval[0] = 0x02;
atval[1] = 0x00;
msg = g_strdup("Enable final measurement");
- gatt_write_char(t->attrib, desc->handle, atval, 2,
- final_measurement_cb, msg);
+ gatt_write_char(t->attrib, desc->handle, atval, 2, measurement_cb, msg);
+}
+
+static void enable_intermediate_measurement(struct thermometer *t)
+{
+ struct characteristic *ch;
+ struct descriptor *desc;
+ bt_uuid_t btuuid;
+ uint8_t atval[2];
+ gchar *msg;
+
+ ch = get_characteristic(t, INTERMEDIATE_TEMPERATURE_UUID);
+ if (ch == NULL) {
+ DBG("Intermediate 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;
+ }
+
+ atval[0] = 0x01;
+ atval[1] = 0x00;
+ msg = g_strdup("Enable intermediate measurement");
+ gatt_write_char(t->attrib, desc->handle, atval, 2, measurement_cb, msg);
}
static void disable_final_measurement(struct thermometer *t)
@@ -620,8 +647,7 @@ static void disable_final_measurement(struct thermometer *t)
atval[0] = 0x00;
atval[1] = 0x00;
msg = g_strdup("Disable final measurement");
- gatt_write_char(t->attrib, desc->handle, atval, 2,
- final_measurement_cb, msg);
+ gatt_write_char(t->attrib, desc->handle, atval, 2, measurement_cb, msg);
}
static void watcher_exit(DBusConnection *conn, void *user_data)
@@ -638,7 +664,7 @@ static void watcher_exit(DBusConnection *conn, void *user_data)
disable_final_measurement(t);
}
-static struct watcher *find_watcher(struct thermometer *t, const gchar *sender,
+static struct watcher *find_watcher(GSList *list, const gchar *sender,
const gchar *path)
{
struct watcher *match;
@@ -648,7 +674,7 @@ static struct watcher *find_watcher(struct thermometer *t, const gchar *sender,
match->srv = g_strdup(sender);
match->path = g_strdup(path);
- l = g_slist_find_custom(t->fwatchers, match, cmp_watcher);
+ l = g_slist_find_custom(list, match, cmp_watcher);
destroy_watcher(match);
if (l != NULL)
@@ -669,7 +695,7 @@ static DBusMessage *register_watcher(DBusConnection *conn, DBusMessage *msg,
DBUS_TYPE_INVALID))
return btd_error_invalid_args(msg);
- watcher = find_watcher(t, sender, path);
+ watcher = find_watcher(t->fwatchers, sender, path);
if (watcher != NULL)
return btd_error_already_exists(msg);
@@ -702,7 +728,7 @@ static DBusMessage *unregister_watcher(DBusConnection *conn, DBusMessage *msg,
DBUS_TYPE_INVALID))
return btd_error_invalid_args(msg);
- watcher = find_watcher(t, sender, path);
+ watcher = find_watcher(t->fwatchers, sender, path);
if (watcher == NULL)
return btd_error_does_not_exist(msg);
@@ -720,9 +746,33 @@ static DBusMessage *unregister_watcher(DBusConnection *conn, DBusMessage *msg,
static DBusMessage *enable_intermediate(DBusConnection *conn, DBusMessage *msg,
void *data)
{
- /* TODO: */
- return g_dbus_create_error(msg, ERROR_INTERFACE ".ThermometerError",
- "Function not implemented.");
+ const gchar *sender = dbus_message_get_sender(msg);
+ struct thermometer *t = data;
+ struct watcher *watcher;
+ gchar *path;
+
+ if (!t->intermediate)
+ return btd_error_not_supported(msg);
+
+ if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_OBJECT_PATH, &path,
+ DBUS_TYPE_INVALID))
+ return btd_error_invalid_args(msg);
+
+ watcher = find_watcher(t->fwatchers, sender, path);
+ if (watcher == NULL)
+ return btd_error_does_not_exist(msg);
+
+ if (find_watcher(t->iwatchers, sender, path))
+ return btd_error_already_exists(msg);
+
+ DBG("Intermediate measurement watcher %s registered", path);
+
+ if (g_slist_length(t->iwatchers) == 0)
+ enable_intermediate_measurement(t);
+
+ t->iwatchers = g_slist_prepend(t->iwatchers, watcher);
+
+ return dbus_message_new_method_return(msg);
}
static DBusMessage *disable_intermediate(DBusConnection *conn, DBusMessage *msg,
--
1.7.7.2
---
src/bluetooth.conf | 1 +
1 files changed, 1 insertions(+), 0 deletions(-)
diff --git a/src/bluetooth.conf b/src/bluetooth.conf
index b5a6af3..664dbd9 100644
--- a/src/bluetooth.conf
+++ b/src/bluetooth.conf
@@ -15,6 +15,7 @@
<allow send_interface="org.bluez.MediaEndpoint"/>
<allow send_interface="org.bluez.MediaPlayer"/>
<allow send_interface="org.bluez.Watcher"/>
+ <allow send_interface="org.bluez.ThermometerWatcher"/>
</policy>
<policy at_console="true">
--
1.7.7.2
---
thermometer/thermometer.c | 57 ++++++++++++++++++++++++++++++++++++++++++++-
1 files changed, 56 insertions(+), 1 deletions(-)
diff --git a/thermometer/thermometer.c b/thermometer/thermometer.c
index 5b0e30a..0928f15 100644
--- a/thermometer/thermometer.c
+++ b/thermometer/thermometer.c
@@ -163,6 +163,17 @@ static gint cmp_char_uuid(gconstpointer a, gconstpointer b)
return g_strcmp0(ch->attr.uuid, uuid);
}
+static gint cmp_char_val_handle(gconstpointer a, gconstpointer b)
+{
+ const struct characteristic *ch = a;
+ const uint16_t *handle = b;
+
+ if (ch->attr.value_handle == *handle)
+ return 0;
+
+ return -1;
+}
+
static gint cmp_descriptor(gconstpointer a, gconstpointer b)
{
const struct descriptor *desc = a;
@@ -703,9 +714,53 @@ static GDBusSignalTable thermometer_signals[] = {
{ }
};
+static void proc_measurement(struct thermometer *t, const uint8_t *pdu,
+ uint16_t len, gboolean final)
+{
+ DBG("TODO: Process measurement indication");
+}
+
+static void proc_measurement_interval(struct thermometer *t, const uint8_t *pdu,
+ uint16_t len)
+{
+ DBG("TODO: Process measurements interval indication");
+}
+
static void ind_handler(const uint8_t *pdu, uint16_t len, gpointer user_data)
{
- /* TODO: Process indication */
+ struct thermometer *t = user_data;
+ const struct characteristic *ch;
+ uint8_t opdu[ATT_MAX_MTU];
+ uint16_t handle, olen;
+ GSList *l;
+
+ if (len < 3) {
+ DBG("Bad pdu received");
+ goto done;
+ }
+
+ if (pdu[0] != ATT_OP_HANDLE_IND) {
+ DBG("Not indication received");
+ return;
+ }
+
+ handle = att_get_u16(&pdu[1]);
+ l = g_slist_find_custom(t->chars, &handle, cmp_char_val_handle);
+ if (l == NULL)
+ goto done;
+
+ ch = l->data;
+ if (g_strcmp0(ch->attr.uuid, TEMPERATURE_MEASUREMENT_UUID) == 0)
+ proc_measurement(t, pdu, len, TRUE);
+ else if (g_strcmp0(ch->attr.uuid, MEASUREMENT_INTERVAL_UUID) == 0)
+ proc_measurement_interval(t, pdu, len);
+
+done:
+ olen = enc_confirmation(opdu, sizeof(opdu));
+
+ if (olen > 0)
+ g_attrib_send(t->attrib, 0, opdu[0], opdu, olen, NULL, NULL,
+ NULL);
}
static void attio_connected_cb(GAttrib *attrib, gpointer user_data)
--
1.7.7.2