/* * * 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 #endif #include #include #include #include #include #include "plugin.h" #include "adapter.h" #include "device.h" #include "profile.h" #include "dbus-common.h" #include "error.h" #include "attrib/gattrib.h" #include "attrib/att.h" #include "attrib/gatt.h" #include "attio.h" #include "log.h" /* min length for ATT indication or notification: opcode (1b) + handle (2b) */ #define ATT_HDR_LEN 3 #define ATT_TIMEOUT 30 #define CYCLINGSPEED_INTERFACE "org.bluez.CyclingSpeed" #define CYCLINGSPEED_MANAGER_INTERFACE "org.bluez.CyclingSpeedManager" #define CYCLINGSPEED_WATCHER_INTERFACE "org.bluez.CyclingSpeedWatcher" #define WHEEL_REV_SUPPORT 0x01 #define CRANK_REV_SUPPORT 0x02 #define MULTI_SENSOR_LOC_SUPPORT 0x04 #define WHEEL_REV_PRESENT 0x01 #define CRANK_REV_PRESENT 0x02 #define SET_CUMULATIVE_VALUE 0x01 #define START_SENSOR_CALIBRATION 0x02 #define UPDATE_SENSOR_LOC 0x03 #define REQUEST_SUPPORTED_SENSOR_LOC 0x04 #define RESPONSE_CODE 0x10 #define RSP_SUCCESS 0x01 #define RSP_NOT_SUPPORTED 0x02 #define RSP_INVALID_PARAM 0x03 #define RSP_FAILED 0x04 struct csc; struct controlpoint_req { struct csc *csc; uint8_t opcode; guint timeout; GDBusPendingReply reply_id; DBusMessage *msg; uint8_t pending_location; }; struct csc_adapter { struct btd_adapter *adapter; GSList *devices; /* list of registered devices */ GSList *watchers; }; struct csc { struct btd_device *dev; struct csc_adapter *cadapter; GAttrib *attrib; guint attioid; /* attio id for measurement characteristics value notifications */ guint attio_measurement_id; /* attio id for SC Control Point characteristics value indications */ guint attio_controlpoint_id; struct att_range *svc_range; uint16_t measurement_ccc_handle; uint16_t controlpoint_val_handle; uint16_t feature; gboolean has_location; uint8_t location; uint8_t num_locations; uint8_t *locations; struct controlpoint_req *pending_req; }; struct watcher { struct csc_adapter *cadapter; guint id; char *srv; char *path; }; struct measurement { struct csc *csc; bool has_wheel_rev; uint32_t wheel_rev; uint16_t last_wheel_time; bool has_crank_rev; uint16_t crank_rev; uint16_t last_crank_time; }; struct characteristic { struct csc *csc; char uuid[MAX_LEN_UUID_STR + 1]; }; static GSList *csc_adapters = NULL; static const char * const location_enum[] = { "other", "top-of-shoe", "in-shoe", "hip", "front-wheel", "left-crank", "right-crank", "left-pedal", "right-pedal", "front-hub", "rear-dropout", "chainstay", "rear-wheel", "rear-hub" }; static const gchar *location2str(uint8_t value) { if (value < G_N_ELEMENTS(location_enum)) return location_enum[value]; info("Body Sensor Location [%d] is RFU", value); return location_enum[0]; } static int str2location(const char *location) { size_t i; for (i = 0; i < G_N_ELEMENTS(location_enum); i++) if (!strcmp(location_enum[i], location)) return i; return -1; } static gint cmp_adapter(gconstpointer a, gconstpointer b) { const struct csc_adapter *cadapter = a; const struct btd_adapter *adapter = b; if (adapter == cadapter->adapter) return 0; return -1; } static gint cmp_device(gconstpointer a, gconstpointer b) { const struct csc *csc = a; const struct btd_device *dev = b; if (dev == csc->dev) return 0; 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 csc_adapter *find_csc_adapter(struct btd_adapter *adapter) { GSList *l = g_slist_find_custom(csc_adapters, adapter, cmp_adapter); if (!l) return NULL; return l->data; } static void destroy_watcher(gpointer user_data) { struct watcher *watcher = user_data; g_free(watcher->path); g_free(watcher->srv); g_free(watcher); } 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 void remove_watcher(gpointer user_data) { struct watcher *watcher = user_data; g_dbus_remove_watch(btd_get_dbus_connection(), watcher->id); } static void destroy_csc_adapter(gpointer user_data) { struct csc_adapter *cadapter = user_data; g_slist_free_full(cadapter->watchers, remove_watcher); g_free(cadapter); } static void destroy_csc(gpointer user_data) { struct csc *csc = user_data; if (csc->attioid > 0) btd_device_remove_attio_callback(csc->dev, csc->attioid); if (csc->attrib != NULL) { g_attrib_unregister(csc->attrib, csc->attio_measurement_id); g_attrib_unregister(csc->attrib, csc->attio_controlpoint_id); g_attrib_unref(csc->attrib); } btd_device_unref(csc->dev); g_free(csc->svc_range); g_free(csc->locations); g_free(csc); } 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 gboolean controlpoint_timeout(gpointer user_data) { struct controlpoint_req *req = user_data; if (req->opcode == UPDATE_SENSOR_LOC) { g_dbus_pending_property_error(req->reply_id, ERROR_INTERFACE ".Failed", "Operation failed (timeout)"); } else if (req->opcode == SET_CUMULATIVE_VALUE) { DBusMessage *reply; reply = btd_error_failed(req->msg, "Operation failed (timeout)"); g_dbus_send_message(btd_get_dbus_connection(), reply); dbus_message_unref(req->msg); } req->csc->pending_req = NULL; g_free(req); return FALSE; } static void controlpoint_write_cb(guint8 status, const guint8 *pdu, guint16 len, gpointer user_data) { struct controlpoint_req *req = user_data; if (status == 0) { req->timeout = g_timeout_add_seconds(ATT_TIMEOUT, controlpoint_timeout, req); return; } error("SC Control Point write failed (opcode=%d)", req->opcode); if (req->opcode == UPDATE_SENSOR_LOC) { g_dbus_pending_property_error(req->reply_id, ERROR_INTERFACE ".Failed", "Operation failed (%d)", status); } else if (req->opcode == SET_CUMULATIVE_VALUE) { DBusMessage *reply; reply = btd_error_failed(req->msg, "Operation failed"); g_dbus_send_message(btd_get_dbus_connection(), reply); dbus_message_unref(req->msg); } req->csc->pending_req = NULL; g_free(req); } static void read_supported_locations(struct csc *csc) { struct controlpoint_req *req; req = g_new0(struct controlpoint_req, 1); req->csc = csc; req->opcode = REQUEST_SUPPORTED_SENSOR_LOC; csc->pending_req = req; gatt_write_char(csc->attrib, csc->controlpoint_val_handle, &req->opcode, sizeof(req->opcode), controlpoint_write_cb, req); } static void read_feature_cb(guint8 status, const guint8 *pdu, guint16 len, gpointer user_data) { struct csc *csc = user_data; uint8_t value[2]; ssize_t vlen; if (status) { error("CSC Feature 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 != sizeof(value)) { error("Invalid value length for CSC Feature"); return; } csc->feature = att_get_u16(value); if ((csc->feature & MULTI_SENSOR_LOC_SUPPORT) && (csc->locations == NULL)) read_supported_locations(csc); } static void read_location_cb(guint8 status, const guint8 *pdu, guint16 len, gpointer user_data) { struct csc *csc = user_data; uint8_t value; ssize_t vlen; if (status) { error("Sensor Location 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 != sizeof(value)) { error("Invalid value length for Sensor Location"); return; } csc->has_location = TRUE; csc->location = value; g_dbus_emit_property_changed(btd_get_dbus_connection(), device_get_path(csc->dev), CYCLINGSPEED_INTERFACE, "Location"); } 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 %s descriptors failed: %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; uint8_t attr_val[2]; char *msg; value = list->data[i]; handle = att_get_u16(value); uuid = att_get_u16(value + 2); if (uuid != GATT_CLIENT_CHARAC_CFG_UUID) continue; if (g_strcmp0(ch->uuid, CSC_MEASUREMENT_UUID) == 0) { ch->csc->measurement_ccc_handle = handle; if (g_slist_length(ch->csc->cadapter->watchers) == 0) { att_put_u16(0x0000, attr_val); msg = g_strdup("Disable measurement"); } else { att_put_u16(GATT_CLIENT_CHARAC_CFG_NOTIF_BIT, attr_val); msg = g_strdup("Enable measurement"); } } else if (g_strcmp0(ch->uuid, SC_CONTROL_POINT_UUID) == 0) { att_put_u16(GATT_CLIENT_CHARAC_CFG_IND_BIT, attr_val); msg = g_strdup("Enable SC Control Point indications"); } else { break; } gatt_write_char(ch->csc->attrib, handle, attr_val, sizeof(attr_val), char_write_cb, msg); /* We only want CCC, can break here */ break; } done: if (list) att_data_list_free(list); g_free(ch); } static void discover_desc(struct csc *csc, 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 != csc->svc_range->end) { end = csc->svc_range->end; } else { return; } ch = g_new0(struct characteristic, 1); ch->csc = csc; memcpy(ch->uuid, c->uuid, sizeof(c->uuid)); gatt_find_info(csc->attrib, start, end, discover_desc_cb, ch); } static void update_watcher(gpointer data, gpointer user_data) { struct watcher *w = data; struct measurement *m = user_data; struct csc *csc = m->csc; const gchar *path = device_get_path(csc->dev); DBusMessageIter iter; DBusMessageIter dict; DBusMessage *msg; msg = dbus_message_new_method_call(w->srv, w->path, CYCLINGSPEED_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); if (m->has_wheel_rev) { dict_append_entry(&dict, "WheelRevolutions", DBUS_TYPE_UINT32, &m->wheel_rev); dict_append_entry(&dict, "LastWheelEventTime", DBUS_TYPE_UINT16, &m->last_wheel_time); } if (m->has_crank_rev) { dict_append_entry(&dict, "CrankRevolutions", DBUS_TYPE_UINT16, &m->crank_rev); dict_append_entry(&dict, "LastCrankEventTime", DBUS_TYPE_UINT16, &m->last_crank_time); } 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 process_measurement(struct csc *csc, const uint8_t *pdu, uint16_t len) { struct measurement m; uint8_t flags; flags = *pdu; pdu++; len--; memset(&m, 0, sizeof(m)); if ((flags & WHEEL_REV_PRESENT) && (csc->feature & WHEEL_REV_SUPPORT)) { if (len < 6) { error("Wheel revolutions data fields missing"); return; } m.has_wheel_rev = true; m.wheel_rev = att_get_u32(pdu); m.last_wheel_time = att_get_u16(pdu + 4); pdu += 6; len -= 6; } if ((flags & CRANK_REV_PRESENT) && (csc->feature & CRANK_REV_SUPPORT)) { if (len < 4) { error("Crank revolutions data fields missing"); return; } m.has_crank_rev = true; m.crank_rev = att_get_u16(pdu); m.last_crank_time = att_get_u16(pdu + 2); pdu += 4; len -= 4; } /* Notify all registered watchers */ m.csc = csc; g_slist_foreach(csc->cadapter->watchers, update_watcher, &m); } static void measurement_notify_handler(const uint8_t *pdu, uint16_t len, gpointer user_data) { struct csc *csc = user_data; /* should be at least opcode (1b) + handle (2b) */ if (len < 3) { error("Invalid PDU received"); return; } process_measurement(csc, pdu + 3, len - 3); } static void controlpoint_property_reply(struct controlpoint_req *req, uint8_t code) { switch (code) { case RSP_SUCCESS: g_dbus_pending_property_success(req->reply_id); break; case RSP_NOT_SUPPORTED: g_dbus_pending_property_error(req->reply_id, ERROR_INTERFACE ".NotSupported", "Feature is not supported"); break; case RSP_INVALID_PARAM: g_dbus_pending_property_error(req->reply_id, ERROR_INTERFACE ".InvalidArguments", "Invalid arguments in method call"); break; case RSP_FAILED: g_dbus_pending_property_error(req->reply_id, ERROR_INTERFACE ".Failed", "Operation failed"); break; default: g_dbus_pending_property_error(req->reply_id, ERROR_INTERFACE ".Failed", "Operation failed (%d)", code); break; } } static void controlpoint_method_reply(struct controlpoint_req *req, uint8_t code) { DBusMessage *reply; switch (code) { case RSP_SUCCESS: reply = dbus_message_new_method_return(req->msg); break; case RSP_NOT_SUPPORTED: reply = btd_error_not_supported(req->msg); break; case RSP_INVALID_PARAM: reply = btd_error_invalid_args(req->msg); break; case RSP_FAILED: reply = btd_error_failed(req->msg, "Failed"); break; default: reply = btd_error_failed(req->msg, "Unknown error"); break; } g_dbus_send_message(btd_get_dbus_connection(), reply); dbus_message_unref(req->msg); } static void controlpoint_ind_handler(const uint8_t *pdu, uint16_t len, gpointer user_data) { struct csc *csc = user_data; struct controlpoint_req *req = csc->pending_req; uint8_t opcode; uint8_t req_opcode; uint8_t rsp_code; uint8_t *opdu; uint16_t olen; size_t plen; if (len < ATT_HDR_LEN) { error("Invalid PDU received"); return; } /* skip ATT header */ pdu += ATT_HDR_LEN; len -= ATT_HDR_LEN; if (len < 1) { error("Op Code missing"); goto done; } opcode = *pdu; pdu++; len--; if (opcode != RESPONSE_CODE) { DBG("Unsupported Op Code received (%d)", opcode); goto done; } if (len < 2) { error("Invalid Response Code PDU received"); goto done; } req_opcode = *pdu; rsp_code = *(pdu + 1); pdu += 2; len -= 2; if (req == NULL || req->opcode != req_opcode) { DBG("Indication received without pending request"); goto done; } switch (req->opcode) { case SET_CUMULATIVE_VALUE: controlpoint_method_reply(req, rsp_code); break; case REQUEST_SUPPORTED_SENSOR_LOC: if (rsp_code == RSP_SUCCESS) { csc->num_locations = len; csc->locations = g_memdup(pdu, len); } else { error("Failed to read Supported Sendor Locations"); } break; case UPDATE_SENSOR_LOC: csc->location = req->pending_location; controlpoint_property_reply(req, rsp_code); g_dbus_emit_property_changed(btd_get_dbus_connection(), device_get_path(csc->dev), CYCLINGSPEED_INTERFACE, "Location"); break; } csc->pending_req = NULL; g_source_remove(req->timeout); g_free(req); done: opdu = g_attrib_get_buffer(csc->attrib, &plen); olen = enc_confirmation(opdu, plen); if (olen > 0) g_attrib_send(csc->attrib, 0, opdu, olen, NULL, NULL, NULL); } static void discover_char_cb(GSList *chars, guint8 status, gpointer user_data) { struct csc *csc = user_data; uint16_t feature_val_handle = 0; if (status) { error("Discover CSCS characteristics: %s", att_ecode2str(status)); return; } for (; chars; chars = chars->next) { struct gatt_char *c = chars->data; struct gatt_char *c_next = (chars->next ? chars->next->data : NULL); if (g_strcmp0(c->uuid, CSC_MEASUREMENT_UUID) == 0) { csc->attio_measurement_id = g_attrib_register(csc->attrib, ATT_OP_HANDLE_NOTIFY, c->value_handle, measurement_notify_handler, csc, NULL); discover_desc(csc, c, c_next); } else if (g_strcmp0(c->uuid, CSC_FEATURE_UUID) == 0) { feature_val_handle = c->value_handle; } else if (g_strcmp0(c->uuid, SENSOR_LOCATION_UUID) == 0) { DBG("Sensor Location supported"); gatt_read_char(csc->attrib, c->value_handle, read_location_cb, csc); } else if (g_strcmp0(c->uuid, SC_CONTROL_POINT_UUID) == 0) { DBG("SC Control Point supported"); csc->controlpoint_val_handle = c->value_handle; csc->attio_controlpoint_id = g_attrib_register( csc->attrib, ATT_OP_HANDLE_IND, c->value_handle, controlpoint_ind_handler, csc, NULL); discover_desc(csc, c, c_next); } } if (feature_val_handle > 0) gatt_read_char(csc->attrib, feature_val_handle, read_feature_cb, csc); } static void enable_measurement(gpointer data, gpointer user_data) { struct csc *csc = data; uint16_t handle = csc->measurement_ccc_handle; uint8_t value[2]; char *msg; if (csc->attrib == NULL || !handle) return; att_put_u16(GATT_CLIENT_CHARAC_CFG_NOTIF_BIT, value); msg = g_strdup("Enable measurement"); gatt_write_char(csc->attrib, handle, value, sizeof(value), char_write_cb, msg); } static void disable_measurement(gpointer data, gpointer user_data) { struct csc *csc = data; uint16_t handle = csc->measurement_ccc_handle; uint8_t value[2]; char *msg; if (csc->attrib == NULL || !handle) return; att_put_u16(0x0000, value); msg = g_strdup("Disable measurement"); gatt_write_char(csc->attrib, handle, value, sizeof(value), char_write_cb, msg); } static void attio_connected_cb(GAttrib *attrib, gpointer user_data) { struct csc *csc = user_data; DBG(""); csc->attrib = g_attrib_ref(attrib); gatt_discover_char(csc->attrib, csc->svc_range->start, csc->svc_range->end, NULL, discover_char_cb, csc); } static void attio_disconnected_cb(gpointer user_data) { struct csc *csc = user_data; DBG(""); if (csc->attio_measurement_id > 0) { g_attrib_unregister(csc->attrib, csc->attio_measurement_id); csc->attio_measurement_id = 0; } if (csc->attio_controlpoint_id > 0) { g_attrib_unregister(csc->attrib, csc->attio_controlpoint_id); csc->attio_controlpoint_id = 0; } g_attrib_unref(csc->attrib); csc->attrib = NULL; } static void watcher_exit_cb(DBusConnection *conn, void *user_data) { struct watcher *watcher = user_data; struct csc_adapter *cadapter = watcher->cadapter; DBG("cycling watcher [%s] disconnected", watcher->path); cadapter->watchers = g_slist_remove(cadapter->watchers, watcher); g_dbus_remove_watch(conn, watcher->id); if (g_slist_length(cadapter->watchers) == 0) g_slist_foreach(cadapter->devices, disable_measurement, 0); } static DBusMessage *register_watcher(DBusConnection *conn, DBusMessage *msg, void *data) { struct csc_adapter *cadapter = data; struct watcher *watcher; const char *sender = dbus_message_get_sender(msg); 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(cadapter->watchers, sender, path); if (watcher != NULL) return btd_error_already_exists(msg); watcher = g_new0(struct watcher, 1); watcher->cadapter = cadapter; watcher->id = g_dbus_add_disconnect_watch(conn, sender, watcher_exit_cb, watcher, destroy_watcher); watcher->srv = g_strdup(sender); watcher->path = g_strdup(path); if (g_slist_length(cadapter->watchers) == 0) g_slist_foreach(cadapter->devices, enable_measurement, 0); cadapter->watchers = g_slist_prepend(cadapter->watchers, watcher); DBG("cycling watcher [%s] registered", path); return dbus_message_new_method_return(msg); } static DBusMessage *unregister_watcher(DBusConnection *conn, DBusMessage *msg, void *data) { struct csc_adapter *cadapter = data; struct watcher *watcher; const char *sender = dbus_message_get_sender(msg); 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(cadapter->watchers, sender, path); if (watcher == NULL) return btd_error_does_not_exist(msg); cadapter->watchers = g_slist_remove(cadapter->watchers, watcher); g_dbus_remove_watch(conn, watcher->id); if (g_slist_length(cadapter->watchers) == 0) g_slist_foreach(cadapter->devices, disable_measurement, 0); DBG("cycling watcher [%s] unregistered", path); return dbus_message_new_method_return(msg); } static const GDBusMethodTable cyclingspeed_manager_methods[] = { { GDBUS_METHOD("RegisterWatcher", GDBUS_ARGS({ "agent", "o" }), NULL, register_watcher) }, { GDBUS_METHOD("UnregisterWatcher", GDBUS_ARGS({ "agent", "o" }), NULL, unregister_watcher) }, { } }; static int csc_adapter_probe(struct btd_profile *p, struct btd_adapter *adapter) { struct csc_adapter *cadapter; cadapter = g_new0(struct csc_adapter, 1); cadapter->adapter = adapter; csc_adapters = g_slist_prepend(csc_adapters, cadapter); if (!g_dbus_register_interface(btd_get_dbus_connection(), adapter_get_path(adapter), CYCLINGSPEED_MANAGER_INTERFACE, cyclingspeed_manager_methods, NULL, NULL, cadapter, destroy_csc_adapter)) { error("D-Bus failed to register %s interface", CYCLINGSPEED_MANAGER_INTERFACE); destroy_csc_adapter(cadapter); return -EIO; } return 0; } static void csc_adapter_remove(struct btd_profile *p, struct btd_adapter *adapter) { struct csc_adapter *cadapter; cadapter = find_csc_adapter(adapter); if (cadapter == NULL) return; csc_adapters = g_slist_remove(csc_adapters, cadapter); g_dbus_unregister_interface(btd_get_dbus_connection(), adapter_get_path(cadapter->adapter), CYCLINGSPEED_MANAGER_INTERFACE); } static gboolean property_get_location(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct csc *csc = data; const char *loc; if (!csc->has_location) return FALSE; loc = location2str(csc->location); dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &loc); return TRUE; } static void property_set_location(const GDBusPropertyTable *property, DBusMessageIter *iter, GDBusPendingPropertySet id, void *data) { struct csc *csc = data; char *loc; int loc_val; uint8_t att_val[2]; struct controlpoint_req *req; if (csc->pending_req != NULL) return g_dbus_pending_property_error(id, ERROR_INTERFACE ".InProgress", "Operation already in progress"); if (!(csc->feature & MULTI_SENSOR_LOC_SUPPORT)) return g_dbus_pending_property_error(id, ERROR_INTERFACE ".NotSupported", "Feature is not supported"); if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_STRING) return g_dbus_pending_property_error(id, ERROR_INTERFACE ".InvalidArguments", "Invalid arguments in method call"); dbus_message_iter_get_basic(iter, &loc); loc_val = str2location(loc); if (loc_val < 0) return g_dbus_pending_property_error(id, ERROR_INTERFACE ".InvalidArguments", "Invalid arguments in method call"); req = g_new(struct controlpoint_req, 1); req->csc = csc; req->reply_id = id; req->opcode = UPDATE_SENSOR_LOC; req->pending_location = loc_val; csc->pending_req = req; att_val[0] = UPDATE_SENSOR_LOC; att_val[1] = loc_val; gatt_write_char(csc->attrib, csc->controlpoint_val_handle, att_val, sizeof(att_val), controlpoint_write_cb, req); } static gboolean property_exists_location(const GDBusPropertyTable *property, void *data) { struct csc *csc = data; return csc->has_location; } static gboolean property_get_locations(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct csc *csc = data; DBusMessageIter entry; int i; if (!(csc->feature & MULTI_SENSOR_LOC_SUPPORT)) return FALSE; dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, DBUS_TYPE_STRING_AS_STRING, &entry); for (i = 0; i < csc->num_locations; i++) { char *loc = g_strdup(location2str(csc->locations[i])); dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &loc); g_free(loc); } dbus_message_iter_close_container(iter, &entry); return TRUE; } static gboolean property_exists_locations(const GDBusPropertyTable *property, void *data) { struct csc *csc = data; return !!(csc->feature & MULTI_SENSOR_LOC_SUPPORT); } static gboolean property_get_wheel_rev_sup(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct csc *csc = data; dbus_bool_t val; val = !!(csc->feature & WHEEL_REV_SUPPORT); dbus_message_iter_append_basic(iter, DBUS_TYPE_BOOLEAN, &val); return TRUE; } static gboolean property_get_multi_loc_sup(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct csc *csc = data; dbus_bool_t val; val = !!(csc->feature & MULTI_SENSOR_LOC_SUPPORT); dbus_message_iter_append_basic(iter, DBUS_TYPE_BOOLEAN, &val); return TRUE; } static const GDBusPropertyTable cyclingspeed_device_properties[] = { { "Location", "s", property_get_location, property_set_location, property_exists_location }, { "SupportedLocations", "as", property_get_locations, NULL, property_exists_locations }, { "WheelRevolutionDataSupported", "b", property_get_wheel_rev_sup }, { "MultipleLocationsSupported", "b", property_get_multi_loc_sup }, { } }; static DBusMessage *set_cumulative_wheel_rev(DBusConnection *conn, DBusMessage *msg, void *data) { struct csc *csc = data; dbus_uint32_t value; struct controlpoint_req *req; uint8_t att_val[5]; /* uint8 opcode + uint32 value */ if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_UINT32, &value, DBUS_TYPE_INVALID)) return btd_error_invalid_args(msg); if (csc->pending_req != NULL) return btd_error_in_progress(msg); req = g_new(struct controlpoint_req, 1); req->csc = csc; req->opcode = SET_CUMULATIVE_VALUE; req->msg = dbus_message_ref(msg); csc->pending_req = req; att_val[0] = SET_CUMULATIVE_VALUE; att_put_u32(value, att_val + 1); gatt_write_char(csc->attrib, csc->controlpoint_val_handle, att_val, sizeof(att_val), controlpoint_write_cb, req); return NULL; } static const GDBusMethodTable cyclingspeed_device_methods[] = { { GDBUS_ASYNC_METHOD("SetCumulativeWheelRevolutions", GDBUS_ARGS({ "value", "u" }), NULL, set_cumulative_wheel_rev) }, { } }; static gint cmp_primary_uuid(gconstpointer a, gconstpointer b) { const struct gatt_primary *prim = a; const char *uuid = b; return g_strcmp0(prim->uuid, uuid); } static int csc_device_probe(struct btd_profile *p, struct btd_device *device, GSList *uuids) { struct btd_adapter *adapter; struct csc_adapter *cadapter; struct csc *csc; struct gatt_primary *prim; GSList *primaries; GSList *l; primaries = btd_device_get_primaries(device); l = g_slist_find_custom(primaries, CYCLING_SC_UUID, cmp_primary_uuid); if (l == NULL) return -EINVAL; prim = l->data; adapter = device_get_adapter(device); cadapter = find_csc_adapter(adapter); if (cadapter == NULL) return -1; csc = g_new0(struct csc, 1); csc->dev = btd_device_ref(device); csc->cadapter = cadapter; if (!g_dbus_register_interface(btd_get_dbus_connection(), device_get_path(device), CYCLINGSPEED_INTERFACE, cyclingspeed_device_methods, NULL, cyclingspeed_device_properties, csc, destroy_csc)) { error("D-Bus failed to register %s interface", CYCLINGSPEED_INTERFACE); destroy_csc(csc); return -EIO; } csc->svc_range = g_new0(struct att_range, 1); csc->svc_range->start = prim->range.start; csc->svc_range->end = prim->range.end; cadapter->devices = g_slist_prepend(cadapter->devices, csc); csc->attioid = btd_device_add_attio_callback(device, attio_connected_cb, attio_disconnected_cb, csc); return 0; } static void csc_device_remove(struct btd_profile *p, struct btd_device *device) { struct btd_adapter *adapter; struct csc_adapter *cadapter; struct csc *csc; GSList *l; adapter = device_get_adapter(device); cadapter = find_csc_adapter(adapter); if (cadapter == NULL) return; l = g_slist_find_custom(cadapter->devices, device, cmp_device); if (l == NULL) return; csc = l->data; cadapter->devices = g_slist_remove(cadapter->devices, csc); g_dbus_unregister_interface(btd_get_dbus_connection(), device_get_path(device), CYCLINGSPEED_INTERFACE); } static struct btd_profile cscp_profile = { .name = "Cycling Speed and Cadence GATT Driver", .remote_uuids = BTD_UUIDS(CYCLING_SC_UUID), .adapter_probe = csc_adapter_probe, .adapter_remove = csc_adapter_remove, .device_probe = csc_device_probe, .device_remove = csc_device_remove, }; static int cyclingspeed_init(void) { return btd_profile_register(&cscp_profile); } static void cyclingspeed_exit(void) { btd_profile_unregister(&cscp_profile); } BLUETOOTH_PLUGIN_DEFINE(cyclingspeed, VERSION, BLUETOOTH_PLUGIN_PRIORITY_DEFAULT, cyclingspeed_init, cyclingspeed_exit)