| /* |
| * |
| * BlueZ - Bluetooth protocol stack for Linux |
| * |
| * Copyright (C) 2011 GSyC/LibreSoft, Universidad Rey Juan Carlos. |
| * |
| * 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 <stdbool.h> |
| #include <errno.h> |
| |
| #include <gdbus/gdbus.h> |
| |
| #include "lib/uuid.h" |
| #include "plugin.h" |
| #include "dbus-common.h" |
| #include "adapter.h" |
| #include "device.h" |
| #include "profile.h" |
| #include "service.h" |
| #include "error.h" |
| #include "log.h" |
| #include "attrib/gattrib.h" |
| #include "attio.h" |
| #include "attrib/att.h" |
| #include "attrib/gatt.h" |
| |
| #define THERMOMETER_INTERFACE "org.bluez.Thermometer1" |
| #define THERMOMETER_MANAGER_INTERFACE "org.bluez.ThermometerManager1" |
| #define THERMOMETER_WATCHER_INTERFACE "org.bluez.ThermometerWatcher1" |
| |
| /* Temperature measurement flag fields */ |
| #define TEMP_UNITS 0x01 |
| #define TEMP_TIME_STAMP 0x02 |
| #define TEMP_TYPE 0x04 |
| |
| #define FLOAT_MAX_MANTISSA 16777216 /* 2^24 */ |
| |
| #define VALID_RANGE_DESC_SIZE 4 |
| #define TEMPERATURE_TYPE_SIZE 1 |
| #define MEASUREMENT_INTERVAL_SIZE 2 |
| |
| struct thermometer_adapter { |
| struct btd_adapter *adapter; |
| GSList *devices; |
| GSList *fwatchers; /* Final measurements */ |
| GSList *iwatchers; /* Intermediate measurements */ |
| }; |
| |
| struct thermometer { |
| struct btd_device *dev; /* Device reference */ |
| struct thermometer_adapter *tadapter; |
| GAttrib *attrib; /* GATT connection */ |
| struct att_range *svc_range; /* Thermometer range */ |
| guint attioid; /* Att watcher id */ |
| /* attio id for Temperature Measurement value indications */ |
| guint attio_measurement_id; |
| /* attio id for Intermediate Temperature value notifications */ |
| guint attio_intermediate_id; |
| /* attio id for Measurement Interval value indications */ |
| guint attio_interval_id; |
| gboolean intermediate; |
| uint8_t type; |
| uint16_t interval; |
| uint16_t max; |
| uint16_t min; |
| gboolean has_type; |
| gboolean has_interval; |
| |
| uint16_t measurement_ccc_handle; |
| uint16_t intermediate_ccc_handle; |
| uint16_t interval_val_handle; |
| }; |
| |
| struct characteristic { |
| struct thermometer *t; /* Thermometer where the char belongs */ |
| char uuid[MAX_LEN_UUID_STR + 1]; |
| }; |
| |
| struct watcher { |
| struct thermometer_adapter *tadapter; |
| guint id; |
| char *srv; |
| char *path; |
| }; |
| |
| struct measurement { |
| struct thermometer *t; |
| int16_t exp; |
| int32_t mant; |
| uint64_t time; |
| gboolean suptime; |
| char *unit; |
| char *type; |
| char *value; |
| }; |
| |
| struct tmp_interval_data { |
| struct thermometer *thermometer; |
| uint16_t interval; |
| }; |
| |
| static GSList *thermometer_adapters = NULL; |
| |
| static const char * const temp_type[] = { |
| "<reserved>", |
| "armpit", |
| "body", |
| "ear", |
| "finger", |
| "intestines", |
| "mouth", |
| "rectum", |
| "toe", |
| "tympanum" |
| }; |
| |
| static const char *temptype2str(uint8_t value) |
| { |
| if (value > 0 && value < G_N_ELEMENTS(temp_type)) |
| return temp_type[value]; |
| |
| error("Temperature type %d reserved for future use", value); |
| return NULL; |
| } |
| |
| 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 remove_watcher(gpointer user_data) |
| { |
| struct watcher *watcher = user_data; |
| |
| g_dbus_remove_watch(btd_get_dbus_connection(), watcher->id); |
| } |
| |
| static void destroy_thermometer(gpointer user_data) |
| { |
| struct thermometer *t = user_data; |
| |
| if (t->attioid > 0) |
| btd_device_remove_attio_callback(t->dev, t->attioid); |
| |
| if (t->attrib != NULL) { |
| g_attrib_unregister(t->attrib, t->attio_measurement_id); |
| g_attrib_unregister(t->attrib, t->attio_intermediate_id); |
| g_attrib_unregister(t->attrib, t->attio_interval_id); |
| g_attrib_unref(t->attrib); |
| } |
| |
| btd_device_unref(t->dev); |
| g_free(t->svc_range); |
| g_free(t); |
| } |
| |
| static void destroy_thermometer_adapter(gpointer user_data) |
| { |
| struct thermometer_adapter *tadapter = user_data; |
| |
| if (tadapter->devices != NULL) |
| g_slist_free_full(tadapter->devices, destroy_thermometer); |
| |
| if (tadapter->fwatchers != NULL) |
| g_slist_free_full(tadapter->fwatchers, remove_watcher); |
| |
| g_free(tadapter); |
| } |
| |
| static int cmp_adapter(gconstpointer a, gconstpointer b) |
| { |
| const struct thermometer_adapter *tadapter = a; |
| const struct btd_adapter *adapter = b; |
| |
| if (adapter == tadapter->adapter) |
| return 0; |
| |
| return -1; |
| } |
| |
| static int cmp_device(gconstpointer a, gconstpointer b) |
| { |
| const struct thermometer *t = a; |
| const struct btd_device *dev = b; |
| |
| if (dev == t->dev) |
| return 0; |
| |
| return -1; |
| } |
| |
| static int 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 thermometer_adapter * |
| find_thermometer_adapter(struct btd_adapter *adapter) |
| { |
| GSList *l = g_slist_find_custom(thermometer_adapters, adapter, |
| cmp_adapter); |
| if (!l) |
| return NULL; |
| |
| return l->data; |
| } |
| |
| static void change_property(struct thermometer *t, const char *name, |
| gpointer value) { |
| if (g_strcmp0(name, "Intermediate") == 0) { |
| gboolean *intermediate = value; |
| if (t->intermediate == *intermediate) |
| return; |
| |
| t->intermediate = *intermediate; |
| } else if (g_strcmp0(name, "Interval") == 0) { |
| uint16_t *interval = value; |
| if (t->has_interval && t->interval == *interval) |
| return; |
| |
| t->has_interval = TRUE; |
| t->interval = *interval; |
| } else if (g_strcmp0(name, "Maximum") == 0) { |
| uint16_t *max = value; |
| if (t->max == *max) |
| return; |
| |
| t->max = *max; |
| } else if (g_strcmp0(name, "Minimum") == 0) { |
| uint16_t *min = value; |
| if (t->min == *min) |
| return; |
| |
| t->min = *min; |
| } else { |
| DBG("%s is not a thermometer property", name); |
| return; |
| } |
| |
| g_dbus_emit_property_changed(btd_get_dbus_connection(), |
| device_get_path(t->dev), |
| THERMOMETER_INTERFACE, name); |
| } |
| |
| static void update_watcher(gpointer data, gpointer user_data) |
| { |
| struct watcher *w = data; |
| struct measurement *m = user_data; |
| const char *path = device_get_path(m->t->dev); |
| DBusMessageIter iter; |
| DBusMessageIter dict; |
| DBusMessage *msg; |
| |
| msg = dbus_message_new_method_call(w->srv, w->path, |
| THERMOMETER_WATCHER_INTERFACE, |
| "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, "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->value); |
| |
| dbus_message_iter_close_container(&iter, &dict); |
| |
| dbus_message_set_no_reply(msg, TRUE); |
| g_dbus_send_message(btd_get_dbus_connection(), msg); |
| } |
| |
| static void recv_measurement(struct thermometer *t, struct measurement *m) |
| { |
| GSList *wlist; |
| |
| m->t = t; |
| |
| if (g_strcmp0(m->value, "intermediate") == 0) |
| wlist = t->tadapter->iwatchers; |
| else |
| wlist = t->tadapter->fwatchers; |
| |
| g_slist_foreach(wlist, update_watcher, m); |
| } |
| |
| static void proc_measurement(struct thermometer *t, const uint8_t *pdu, |
| uint16_t len, gboolean final) |
| { |
| struct measurement m; |
| const char *type = NULL; |
| uint8_t flags; |
| uint32_t raw; |
| |
| /* skip opcode and handle */ |
| pdu += 3; |
| len -= 3; |
| |
| if (len < 1) { |
| DBG("Mandatory flags are not provided"); |
| return; |
| } |
| |
| memset(&m, 0, sizeof(m)); |
| |
| flags = *pdu; |
| |
| if (flags & TEMP_UNITS) |
| m.unit = "fahrenheit"; |
| else |
| m.unit = "celsius"; |
| |
| pdu++; |
| len--; |
| |
| if (len < 4) { |
| DBG("Mandatory temperature measurement value is not provided"); |
| return; |
| } |
| |
| raw = att_get_u32(pdu); |
| m.mant = raw & 0x00FFFFFF; |
| m.exp = ((int32_t) raw) >> 24; |
| |
| if (m.mant & 0x00800000) { |
| /* convert to C2 negative value */ |
| m.mant = m.mant - FLOAT_MAX_MANTISSA; |
| } |
| |
| pdu += 4; |
| len -= 4; |
| |
| if (flags & TEMP_TIME_STAMP) { |
| struct tm ts; |
| time_t time; |
| |
| if (len < 7) { |
| DBG("Time stamp is not provided"); |
| return; |
| } |
| |
| ts.tm_year = att_get_u16(pdu) - 1900; |
| ts.tm_mon = *(pdu + 2) - 1; |
| ts.tm_mday = *(pdu + 3); |
| ts.tm_hour = *(pdu + 4); |
| ts.tm_min = *(pdu + 5); |
| ts.tm_sec = *(pdu + 6); |
| ts.tm_isdst = -1; |
| |
| time = mktime(&ts); |
| m.time = (uint64_t) time; |
| m.suptime = TRUE; |
| |
| pdu += 7; |
| len -= 7; |
| } |
| |
| if (flags & TEMP_TYPE) { |
| if (len < 1) { |
| DBG("Temperature type is not provided"); |
| return; |
| } |
| |
| type = temptype2str(*pdu); |
| } else if (t->has_type) { |
| type = temptype2str(t->type); |
| } |
| |
| m.type = g_strdup(type); |
| m.value = final ? "final" : "intermediate"; |
| |
| recv_measurement(t, &m); |
| g_free(m.type); |
| } |
| |
| |
| static void measurement_ind_handler(const uint8_t *pdu, uint16_t len, |
| gpointer user_data) |
| { |
| struct thermometer *t = user_data; |
| uint8_t *opdu; |
| uint16_t olen; |
| size_t plen; |
| |
| if (len < 3) { |
| DBG("Bad pdu received"); |
| return; |
| } |
| |
| proc_measurement(t, pdu, len, TRUE); |
| |
| opdu = g_attrib_get_buffer(t->attrib, &plen); |
| olen = enc_confirmation(opdu, plen); |
| |
| if (olen > 0) |
| g_attrib_send(t->attrib, 0, opdu, olen, NULL, NULL, NULL); |
| } |
| |
| static void intermediate_notify_handler(const uint8_t *pdu, uint16_t len, |
| gpointer user_data) |
| { |
| struct thermometer *t = user_data; |
| |
| if (len < 3) { |
| DBG("Bad pdu received"); |
| return; |
| } |
| |
| proc_measurement(t, pdu, len, FALSE); |
| } |
| |
| static void interval_ind_handler(const uint8_t *pdu, uint16_t len, |
| gpointer user_data) |
| { |
| struct thermometer *t = user_data; |
| uint16_t interval; |
| uint8_t *opdu; |
| uint16_t olen; |
| size_t plen; |
| |
| if (len < 5) { |
| DBG("Bad pdu received"); |
| return; |
| } |
| |
| interval = att_get_u16(pdu + 3); |
| change_property(t, "Interval", &interval); |
| |
| opdu = g_attrib_get_buffer(t->attrib, &plen); |
| olen = enc_confirmation(opdu, plen); |
| |
| if (olen > 0) |
| g_attrib_send(t->attrib, 0, opdu, olen, NULL, NULL, NULL); |
| } |
| |
| static void valid_range_desc_cb(guint8 status, const guint8 *pdu, guint16 len, |
| gpointer user_data) |
| { |
| struct thermometer *t = user_data; |
| uint8_t value[VALID_RANGE_DESC_SIZE]; |
| uint16_t max, min; |
| ssize_t vlen; |
| |
| if (status != 0) { |
| DBG("Valid Range descriptor read failed: %s", |
| att_ecode2str(status)); |
| return; |
| } |
| |
| vlen = dec_read_resp(pdu, len, value, sizeof(value)); |
| if (vlen < 0) { |
| DBG("Protocol error\n"); |
| return; |
| } |
| |
| if (vlen < 4) { |
| DBG("Invalid range received"); |
| return; |
| } |
| |
| min = att_get_u16(&value[0]); |
| max = att_get_u16(&value[2]); |
| |
| if (min == 0 || min > max) { |
| DBG("Invalid range"); |
| return; |
| } |
| |
| change_property(t, "Maximum", &max); |
| change_property(t, "Minimum", &min); |
| } |
| |
| static void write_ccc_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 process_thermometer_desc(struct characteristic *ch, uint16_t uuid, |
| uint16_t handle) |
| { |
| uint8_t atval[2]; |
| uint16_t val; |
| char *msg; |
| |
| if (uuid == GATT_CHARAC_VALID_RANGE_UUID) { |
| if (g_strcmp0(ch->uuid, MEASUREMENT_INTERVAL_UUID) == 0) |
| gatt_read_char(ch->t->attrib, handle, |
| valid_range_desc_cb, ch->t); |
| return; |
| } |
| |
| if (uuid != GATT_CLIENT_CHARAC_CFG_UUID) |
| return; |
| |
| if (g_strcmp0(ch->uuid, TEMPERATURE_MEASUREMENT_UUID) == 0) { |
| ch->t->measurement_ccc_handle = handle; |
| |
| if (g_slist_length(ch->t->tadapter->fwatchers) == 0) { |
| val = 0x0000; |
| msg = g_strdup("Disable Temperature Measurement ind"); |
| } else { |
| val = GATT_CLIENT_CHARAC_CFG_IND_BIT; |
| msg = g_strdup("Enable Temperature Measurement ind"); |
| } |
| } else if (g_strcmp0(ch->uuid, INTERMEDIATE_TEMPERATURE_UUID) == 0) { |
| ch->t->intermediate_ccc_handle = handle; |
| |
| if (g_slist_length(ch->t->tadapter->iwatchers) == 0) { |
| val = 0x0000; |
| msg = g_strdup("Disable Intermediate Temperature noti"); |
| } else { |
| val = GATT_CLIENT_CHARAC_CFG_NOTIF_BIT; |
| msg = g_strdup("Enable Intermediate Temperature noti"); |
| } |
| } else if (g_strcmp0(ch->uuid, MEASUREMENT_INTERVAL_UUID) == 0) { |
| val = GATT_CLIENT_CHARAC_CFG_IND_BIT; |
| msg = g_strdup("Enable Measurement Interval indication"); |
| } else { |
| return; |
| } |
| |
| att_put_u16(val, atval); |
| gatt_write_char(ch->t->attrib, handle, atval, sizeof(atval), |
| write_ccc_cb, msg); |
| } |
| |
| 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 = NULL; |
| uint8_t format; |
| int i; |
| |
| if (status != 0) { |
| error("Discover all characteristic descriptors failed [%s]: %s", |
| ch->uuid, att_ecode2str(status)); |
| goto done; |
| } |
| |
| list = dec_find_info_resp(pdu, len, &format); |
| if (list == NULL) |
| goto done; |
| |
| if (format != ATT_FIND_INFO_RESP_FMT_16BIT) |
| goto done; |
| |
| for (i = 0; i < list->num; i++) { |
| uint8_t *value; |
| uint16_t handle, uuid; |
| |
| value = list->data[i]; |
| handle = att_get_u16(value); |
| uuid = att_get_u16(value + 2); |
| |
| process_thermometer_desc(ch, uuid, handle); |
| } |
| |
| done: |
| if (list != NULL) |
| att_data_list_free(list); |
| g_free(ch); |
| } |
| |
| static void discover_desc(struct thermometer *t, struct gatt_char *c, |
| struct gatt_char *c_next) |
| { |
| struct characteristic *ch; |
| uint16_t start, end; |
| |
| start = c->value_handle + 1; |
| |
| if (c_next != NULL) { |
| if (start == c_next->handle) |
| return; |
| end = c_next->handle - 1; |
| } else if (c->value_handle != t->svc_range->end) { |
| end = t->svc_range->end; |
| } else { |
| return; |
| } |
| |
| ch = g_new0(struct characteristic, 1); |
| ch->t = t; |
| memcpy(ch->uuid, c->uuid, sizeof(c->uuid)); |
| |
| gatt_discover_char_desc(t->attrib, start, end, discover_desc_cb, ch); |
| } |
| |
| static void read_temp_type_cb(guint8 status, const guint8 *pdu, guint16 len, |
| gpointer user_data) |
| { |
| struct thermometer *t = user_data; |
| uint8_t value[TEMPERATURE_TYPE_SIZE]; |
| ssize_t vlen; |
| |
| if (status != 0) { |
| DBG("Temperature Type value read failed: %s", |
| att_ecode2str(status)); |
| return; |
| } |
| |
| vlen = dec_read_resp(pdu, len, value, sizeof(value)); |
| if (vlen < 0) { |
| DBG("Protocol error."); |
| return; |
| } |
| |
| if (vlen != 1) { |
| DBG("Invalid length for Temperature type"); |
| return; |
| } |
| |
| t->has_type = TRUE; |
| t->type = value[0]; |
| } |
| |
| static void read_interval_cb(guint8 status, const guint8 *pdu, guint16 len, |
| gpointer user_data) |
| { |
| struct thermometer *t = user_data; |
| uint8_t value[MEASUREMENT_INTERVAL_SIZE]; |
| uint16_t interval; |
| ssize_t vlen; |
| |
| if (status != 0) { |
| DBG("Measurement Interval value read failed: %s", |
| att_ecode2str(status)); |
| return; |
| } |
| |
| vlen = dec_read_resp(pdu, len, value, sizeof(value)); |
| if (vlen < 0) { |
| DBG("Protocol error\n"); |
| return; |
| } |
| |
| if (vlen < 2) { |
| DBG("Invalid Interval received"); |
| return; |
| } |
| |
| interval = att_get_u16(&value[0]); |
| change_property(t, "Interval", &interval); |
| } |
| |
| static void process_thermometer_char(struct thermometer *t, |
| struct gatt_char *c, struct gatt_char *c_next) |
| { |
| if (g_strcmp0(c->uuid, INTERMEDIATE_TEMPERATURE_UUID) == 0) { |
| gboolean intermediate = TRUE; |
| change_property(t, "Intermediate", &intermediate); |
| |
| t->attio_intermediate_id = g_attrib_register(t->attrib, |
| ATT_OP_HANDLE_NOTIFY, c->value_handle, |
| intermediate_notify_handler, t, NULL); |
| |
| discover_desc(t, c, c_next); |
| } else if (g_strcmp0(c->uuid, TEMPERATURE_MEASUREMENT_UUID) == 0) { |
| |
| t->attio_measurement_id = g_attrib_register(t->attrib, |
| ATT_OP_HANDLE_IND, c->value_handle, |
| measurement_ind_handler, t, NULL); |
| |
| discover_desc(t, c, c_next); |
| } else if (g_strcmp0(c->uuid, TEMPERATURE_TYPE_UUID) == 0) { |
| gatt_read_char(t->attrib, c->value_handle, |
| read_temp_type_cb, t); |
| } else if (g_strcmp0(c->uuid, MEASUREMENT_INTERVAL_UUID) == 0) { |
| bool need_desc = false; |
| |
| gatt_read_char(t->attrib, c->value_handle, read_interval_cb, t); |
| |
| if (c->properties & ATT_CHAR_PROPER_WRITE) { |
| t->interval_val_handle = c->value_handle; |
| need_desc = true; |
| } |
| |
| if (c->properties & ATT_CHAR_PROPER_INDICATE) { |
| t->attio_interval_id = g_attrib_register(t->attrib, |
| ATT_OP_HANDLE_IND, c->value_handle, |
| interval_ind_handler, t, NULL); |
| need_desc = true; |
| } |
| |
| if (need_desc) |
| discover_desc(t, c, c_next); |
| } |
| } |
| |
| static void configure_thermometer_cb(GSList *characteristics, guint8 status, |
| gpointer user_data) |
| { |
| struct thermometer *t = user_data; |
| GSList *l; |
| |
| if (status != 0) { |
| error("Discover thermometer characteristics: %s", |
| att_ecode2str(status)); |
| return; |
| } |
| |
| for (l = characteristics; l; l = l->next) { |
| struct gatt_char *c = l->data; |
| struct gatt_char *c_next = (l->next ? l->next->data : NULL); |
| |
| process_thermometer_char(t, c, c_next); |
| } |
| } |
| |
| static void write_interval_cb(guint8 status, const guint8 *pdu, guint16 len, |
| gpointer user_data) |
| { |
| struct tmp_interval_data *data = user_data; |
| |
| if (status != 0) { |
| error("Interval Write Request failed %s", |
| att_ecode2str(status)); |
| goto done; |
| } |
| |
| if (!dec_write_resp(pdu, len)) { |
| error("Interval Write Request: protocol error"); |
| goto done; |
| } |
| |
| change_property(data->thermometer, "Interval", &data->interval); |
| |
| done: |
| g_free(user_data); |
| } |
| |
| static void enable_final_measurement(gpointer data, gpointer user_data) |
| { |
| struct thermometer *t = data; |
| uint16_t handle = t->measurement_ccc_handle; |
| uint8_t value[2]; |
| char *msg; |
| |
| if (t->attrib == NULL || !handle) |
| return; |
| |
| att_put_u16(GATT_CLIENT_CHARAC_CFG_IND_BIT, value); |
| msg = g_strdup("Enable Temperature Measurement indications"); |
| |
| gatt_write_char(t->attrib, handle, value, sizeof(value), |
| write_ccc_cb, msg); |
| } |
| |
| static void enable_intermediate_measurement(gpointer data, gpointer user_data) |
| { |
| struct thermometer *t = data; |
| uint16_t handle = t->intermediate_ccc_handle; |
| uint8_t value[2]; |
| char *msg; |
| |
| if (t->attrib == NULL || !handle) |
| return; |
| |
| att_put_u16(GATT_CLIENT_CHARAC_CFG_NOTIF_BIT, value); |
| msg = g_strdup("Enable Intermediate Temperature notifications"); |
| |
| gatt_write_char(t->attrib, handle, value, sizeof(value), |
| write_ccc_cb, msg); |
| } |
| |
| static void disable_final_measurement(gpointer data, gpointer user_data) |
| { |
| struct thermometer *t = data; |
| uint16_t handle = t->measurement_ccc_handle; |
| uint8_t value[2]; |
| char *msg; |
| |
| if (t->attrib == NULL || !handle) |
| return; |
| |
| att_put_u16(0x0000, value); |
| msg = g_strdup("Disable Temperature Measurement indications"); |
| |
| gatt_write_char(t->attrib, handle, value, sizeof(value), |
| write_ccc_cb, msg); |
| } |
| |
| static void disable_intermediate_measurement(gpointer data, gpointer user_data) |
| { |
| struct thermometer *t = data; |
| uint16_t handle = t->intermediate_ccc_handle; |
| uint8_t value[2]; |
| char *msg; |
| |
| if (t->attrib == NULL || !handle) |
| return; |
| |
| att_put_u16(0x0000, value); |
| msg = g_strdup("Disable Intermediate Temperature notifications"); |
| |
| gatt_write_char(t->attrib, handle, value, sizeof(value), |
| write_ccc_cb, msg); |
| } |
| |
| static void remove_int_watcher(struct thermometer_adapter *tadapter, |
| struct watcher *w) |
| { |
| if (!g_slist_find(tadapter->iwatchers, w)) |
| return; |
| |
| tadapter->iwatchers = g_slist_remove(tadapter->iwatchers, w); |
| |
| if (g_slist_length(tadapter->iwatchers) == 0) |
| g_slist_foreach(tadapter->devices, |
| disable_intermediate_measurement, 0); |
| } |
| |
| static void watcher_exit(DBusConnection *conn, void *user_data) |
| { |
| struct watcher *watcher = user_data; |
| struct thermometer_adapter *tadapter = watcher->tadapter; |
| |
| DBG("Thermometer watcher %s disconnected", watcher->path); |
| |
| remove_int_watcher(tadapter, watcher); |
| |
| tadapter->fwatchers = g_slist_remove(tadapter->fwatchers, watcher); |
| g_dbus_remove_watch(btd_get_dbus_connection(), watcher->id); |
| |
| if (g_slist_length(tadapter->fwatchers) == 0) |
| g_slist_foreach(tadapter->devices, |
| disable_final_measurement, 0); |
| } |
| |
| 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 thermometer_adapter *tadapter = 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(tadapter->fwatchers, sender, path); |
| if (watcher != NULL) |
| return btd_error_already_exists(msg); |
| |
| DBG("Thermometer watcher %s registered", path); |
| |
| watcher = g_new0(struct watcher, 1); |
| watcher->srv = g_strdup(sender); |
| watcher->path = g_strdup(path); |
| watcher->tadapter = tadapter; |
| watcher->id = g_dbus_add_disconnect_watch(conn, sender, watcher_exit, |
| watcher, destroy_watcher); |
| |
| if (g_slist_length(tadapter->fwatchers) == 0) |
| g_slist_foreach(tadapter->devices, enable_final_measurement, 0); |
| |
| tadapter->fwatchers = g_slist_prepend(tadapter->fwatchers, 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 thermometer_adapter *tadapter = 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(tadapter->fwatchers, sender, path); |
| if (watcher == NULL) |
| return btd_error_does_not_exist(msg); |
| |
| DBG("Thermometer watcher %s unregistered", path); |
| |
| remove_int_watcher(tadapter, watcher); |
| |
| tadapter->fwatchers = g_slist_remove(tadapter->fwatchers, watcher); |
| g_dbus_remove_watch(btd_get_dbus_connection(), watcher->id); |
| |
| if (g_slist_length(tadapter->fwatchers) == 0) |
| g_slist_foreach(tadapter->devices, |
| disable_final_measurement, 0); |
| |
| return dbus_message_new_method_return(msg); |
| } |
| |
| static DBusMessage *enable_intermediate(DBusConnection *conn, DBusMessage *msg, |
| void *data) |
| { |
| const char *sender = dbus_message_get_sender(msg); |
| struct thermometer_adapter *ta = 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(ta->fwatchers, sender, path); |
| if (watcher == NULL) |
| return btd_error_does_not_exist(msg); |
| |
| if (find_watcher(ta->iwatchers, sender, path)) |
| return btd_error_already_exists(msg); |
| |
| DBG("Intermediate measurement watcher %s registered", path); |
| |
| if (g_slist_length(ta->iwatchers) == 0) |
| g_slist_foreach(ta->devices, |
| enable_intermediate_measurement, 0); |
| |
| ta->iwatchers = g_slist_prepend(ta->iwatchers, watcher); |
| |
| return dbus_message_new_method_return(msg); |
| } |
| |
| static DBusMessage *disable_intermediate(DBusConnection *conn, DBusMessage *msg, |
| void *data) |
| { |
| const char *sender = dbus_message_get_sender(msg); |
| struct thermometer_adapter *ta = 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(ta->iwatchers, sender, path); |
| if (watcher == NULL) |
| return btd_error_does_not_exist(msg); |
| |
| DBG("Intermediate measurement %s unregistered", path); |
| |
| remove_int_watcher(ta, watcher); |
| |
| return dbus_message_new_method_return(msg); |
| } |
| |
| static gboolean property_get_intermediate(const GDBusPropertyTable *property, |
| DBusMessageIter *iter, void *data) |
| { |
| struct thermometer *t = data; |
| dbus_bool_t val; |
| |
| val = !!t->intermediate; |
| |
| dbus_message_iter_append_basic(iter, DBUS_TYPE_BOOLEAN, &val); |
| |
| return TRUE; |
| } |
| |
| static gboolean property_get_interval(const GDBusPropertyTable *property, |
| DBusMessageIter *iter, void *data) |
| { |
| struct thermometer *t = data; |
| |
| if (!t->has_interval) |
| return FALSE; |
| |
| dbus_message_iter_append_basic(iter, DBUS_TYPE_UINT16, &t->interval); |
| |
| return TRUE; |
| } |
| |
| static void property_set_interval(const GDBusPropertyTable *property, |
| DBusMessageIter *iter, |
| GDBusPendingPropertySet id, void *data) |
| { |
| struct thermometer *t = data; |
| struct tmp_interval_data *interval_data; |
| uint16_t val; |
| uint8_t atval[2]; |
| |
| if (t->interval_val_handle == 0) { |
| g_dbus_pending_property_error(id, |
| ERROR_INTERFACE ".NotSupported", |
| "Operation is not supported"); |
| return; |
| } |
| |
| if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_UINT16) { |
| g_dbus_pending_property_error(id, |
| ERROR_INTERFACE ".InvalidArguments", |
| "Invalid arguments in method call"); |
| return; |
| } |
| |
| dbus_message_iter_get_basic(iter, &val); |
| |
| if (val < t->min || val > t->max) { |
| g_dbus_pending_property_error(id, |
| ERROR_INTERFACE ".InvalidArguments", |
| "Invalid arguments in method call"); |
| return; |
| } |
| |
| att_put_u16(val, &atval[0]); |
| |
| interval_data = g_new0(struct tmp_interval_data, 1); |
| interval_data->thermometer = t; |
| interval_data->interval = val; |
| gatt_write_char(t->attrib, t->interval_val_handle, atval, sizeof(atval), |
| write_interval_cb, interval_data); |
| |
| g_dbus_pending_property_success(id); |
| } |
| |
| static gboolean property_exists_interval(const GDBusPropertyTable *property, |
| void *data) |
| { |
| struct thermometer *t = data; |
| |
| return t->has_interval; |
| } |
| |
| static gboolean property_get_maximum(const GDBusPropertyTable *property, |
| DBusMessageIter *iter, void *data) |
| { |
| struct thermometer *t = data; |
| |
| if (!t->has_interval) |
| return FALSE; |
| |
| dbus_message_iter_append_basic(iter, DBUS_TYPE_UINT16, &t->max); |
| |
| return TRUE; |
| } |
| |
| static gboolean property_get_minimum(const GDBusPropertyTable *property, |
| DBusMessageIter *iter, void *data) |
| { |
| struct thermometer *t = data; |
| |
| if (!t->has_interval) |
| return FALSE; |
| |
| dbus_message_iter_append_basic(iter, DBUS_TYPE_UINT16, &t->min); |
| |
| return TRUE; |
| } |
| |
| static const GDBusPropertyTable thermometer_properties[] = { |
| { "Intermediate", "b", property_get_intermediate }, |
| { "Interval", "q", property_get_interval, property_set_interval, |
| property_exists_interval }, |
| { "Maximum", "q", property_get_maximum, NULL, |
| property_exists_interval }, |
| { "Minimum", "q", property_get_minimum, NULL, |
| property_exists_interval }, |
| { } |
| }; |
| |
| static void attio_connected_cb(GAttrib *attrib, gpointer user_data) |
| { |
| struct thermometer *t = user_data; |
| |
| t->attrib = g_attrib_ref(attrib); |
| |
| gatt_discover_char(t->attrib, t->svc_range->start, t->svc_range->end, |
| NULL, configure_thermometer_cb, t); |
| } |
| |
| static void attio_disconnected_cb(gpointer user_data) |
| { |
| struct thermometer *t = user_data; |
| |
| DBG("GATT Disconnected"); |
| |
| if (t->attio_measurement_id > 0) { |
| g_attrib_unregister(t->attrib, t->attio_measurement_id); |
| t->attio_measurement_id = 0; |
| } |
| |
| if (t->attio_intermediate_id > 0) { |
| g_attrib_unregister(t->attrib, t->attio_intermediate_id); |
| t->attio_intermediate_id = 0; |
| } |
| |
| if (t->attio_interval_id > 0) { |
| g_attrib_unregister(t->attrib, t->attio_interval_id); |
| t->attio_interval_id = 0; |
| } |
| |
| g_attrib_unref(t->attrib); |
| t->attrib = NULL; |
| } |
| |
| static int thermometer_register(struct btd_device *device, |
| struct gatt_primary *tattr) |
| { |
| const char *path = device_get_path(device); |
| struct thermometer *t; |
| struct btd_adapter *adapter; |
| struct thermometer_adapter *tadapter; |
| |
| adapter = device_get_adapter(device); |
| |
| tadapter = find_thermometer_adapter(adapter); |
| |
| if (tadapter == NULL) |
| return -1; |
| |
| t = g_new0(struct thermometer, 1); |
| t->dev = btd_device_ref(device); |
| t->tadapter = tadapter; |
| t->svc_range = g_new0(struct att_range, 1); |
| t->svc_range->start = tattr->range.start; |
| t->svc_range->end = tattr->range.end; |
| |
| tadapter->devices = g_slist_prepend(tadapter->devices, t); |
| |
| if (!g_dbus_register_interface(btd_get_dbus_connection(), |
| path, THERMOMETER_INTERFACE, |
| NULL, NULL, thermometer_properties, |
| t, destroy_thermometer)) { |
| error("D-Bus failed to register %s interface", |
| THERMOMETER_INTERFACE); |
| destroy_thermometer(t); |
| return -EIO; |
| } |
| |
| t->attioid = btd_device_add_attio_callback(device, attio_connected_cb, |
| attio_disconnected_cb, t); |
| return 0; |
| } |
| |
| static void thermometer_unregister(struct btd_device *device) |
| { |
| struct thermometer *t; |
| struct btd_adapter *adapter; |
| struct thermometer_adapter *tadapter; |
| GSList *l; |
| |
| adapter = device_get_adapter(device); |
| |
| tadapter = find_thermometer_adapter(adapter); |
| |
| if (tadapter == NULL) |
| return; |
| |
| l = g_slist_find_custom(tadapter->devices, device, cmp_device); |
| if (l == NULL) |
| return; |
| |
| t = l->data; |
| |
| tadapter->devices = g_slist_remove(tadapter->devices, t); |
| |
| g_dbus_unregister_interface(btd_get_dbus_connection(), |
| device_get_path(t->dev), THERMOMETER_INTERFACE); |
| } |
| |
| static const GDBusMethodTable thermometer_manager_methods[] = { |
| { GDBUS_METHOD("RegisterWatcher", |
| GDBUS_ARGS({ "agent", "o" }), NULL, |
| register_watcher) }, |
| { GDBUS_METHOD("UnregisterWatcher", |
| GDBUS_ARGS({ "agent", "o" }), NULL, |
| unregister_watcher) }, |
| { GDBUS_METHOD("EnableIntermediateMeasurement", |
| GDBUS_ARGS({ "agent", "o" }), NULL, |
| enable_intermediate) }, |
| { GDBUS_METHOD("DisableIntermediateMeasurement", |
| GDBUS_ARGS({ "agent", "o" }), NULL, |
| disable_intermediate) }, |
| { } |
| }; |
| |
| static int thermometer_adapter_register(struct btd_adapter *adapter) |
| { |
| struct thermometer_adapter *tadapter; |
| |
| tadapter = g_new0(struct thermometer_adapter, 1); |
| tadapter->adapter = adapter; |
| |
| if (!g_dbus_register_interface(btd_get_dbus_connection(), |
| adapter_get_path(adapter), |
| THERMOMETER_MANAGER_INTERFACE, |
| thermometer_manager_methods, |
| NULL, NULL, tadapter, |
| destroy_thermometer_adapter)) { |
| error("D-Bus failed to register %s interface", |
| THERMOMETER_MANAGER_INTERFACE); |
| destroy_thermometer_adapter(tadapter); |
| return -EIO; |
| } |
| |
| thermometer_adapters = g_slist_prepend(thermometer_adapters, tadapter); |
| |
| return 0; |
| } |
| |
| static void thermometer_adapter_unregister(struct btd_adapter *adapter) |
| { |
| struct thermometer_adapter *tadapter; |
| |
| tadapter = find_thermometer_adapter(adapter); |
| if (tadapter == NULL) |
| return; |
| |
| thermometer_adapters = g_slist_remove(thermometer_adapters, tadapter); |
| |
| g_dbus_unregister_interface(btd_get_dbus_connection(), |
| adapter_get_path(tadapter->adapter), |
| THERMOMETER_MANAGER_INTERFACE); |
| } |
| |
| static int thermometer_device_probe(struct btd_service *service) |
| { |
| struct btd_device *device = btd_service_get_device(service); |
| struct gatt_primary *tattr; |
| |
| tattr = btd_device_get_primary(device, HEALTH_THERMOMETER_UUID); |
| if (tattr == NULL) |
| return -EINVAL; |
| |
| return thermometer_register(device, tattr); |
| } |
| |
| static void thermometer_device_remove(struct btd_service *service) |
| { |
| struct btd_device *device = btd_service_get_device(service); |
| |
| thermometer_unregister(device); |
| } |
| |
| static int thermometer_adapter_probe(struct btd_profile *p, |
| struct btd_adapter *adapter) |
| { |
| return thermometer_adapter_register(adapter); |
| } |
| |
| static void thermometer_adapter_remove(struct btd_profile *p, |
| struct btd_adapter *adapter) |
| { |
| thermometer_adapter_unregister(adapter); |
| } |
| |
| static struct btd_profile thermometer_profile = { |
| .name = "Health Thermometer GATT driver", |
| .remote_uuid = HEALTH_THERMOMETER_UUID, |
| .device_probe = thermometer_device_probe, |
| .device_remove = thermometer_device_remove, |
| .adapter_probe = thermometer_adapter_probe, |
| .adapter_remove = thermometer_adapter_remove |
| }; |
| |
| static int thermometer_init(void) |
| { |
| return btd_profile_register(&thermometer_profile); |
| } |
| |
| static void thermometer_exit(void) |
| { |
| btd_profile_unregister(&thermometer_profile); |
| } |
| |
| BLUETOOTH_PLUGIN_DEFINE(thermometer, VERSION, BLUETOOTH_PLUGIN_PRIORITY_DEFAULT, |
| thermometer_init, thermometer_exit) |