diff --git a/profiles/cyclingspeed/cyclingspeed.c b/profiles/cyclingspeed/cyclingspeed.c
index eace558..866fac8 100644
--- a/profiles/cyclingspeed/cyclingspeed.c
+++ b/profiles/cyclingspeed/cyclingspeed.c
#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_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;
+};
+
struct csc_adapter {
struct btd_adapter *adapter;
GSList *devices; /* list of registered devices */
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 feature;
gboolean has_location;
uint8_t location;
+
+ struct controlpoint_req *pending_req;
};
struct watcher {
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);
}
g_free(msg);
}
+static gboolean controlpoint_timeout(gpointer user_data)
+{
+ struct controlpoint_req *req = user_data;
+
+ req->csc->pending_req = NULL;
+ g_free(req);
+
+ return FALSE;
+}
+
+__attribute__((unused)) /* TODO: remove once controlpoint ops are implemented */
+static void controlpoint_write_cb(guint8 status, const guint8 *pdu, guint16 len,
+ gpointer user_data)
+{
+ struct controlpoint_req *req = user_data;
+
+ if (status != 0) {
+ error("SC Control Point write failed (opcode=%d)", req->opcode);
+
+ req->csc->pending_req = NULL;
+ g_free(req);
+
+ return;
+ }
+
+ req->timeout = g_timeout_add_seconds(ATT_TIMEOUT, controlpoint_timeout,
+ req);
+}
+
static void read_feature_cb(guint8 status, const guint8 *pdu,
guint16 len, gpointer user_data)
{
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);
continue;
if (g_strcmp0(ch->uuid, CSC_MEASUREMENT_UUID) == 0) {
- char *msg;
- uint8_t attr_val[2];
-
ch->csc->measurement_ccc_handle = handle;
if (g_slist_length(ch->csc->cadapter->watchers) == 0) {
msg = g_strdup("Enable measurement");
}
- gatt_write_char(ch->csc->attrib, handle, attr_val,
- sizeof(attr_val), char_write_cb, msg);
+ } 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;
}
process_measurement(csc, pdu + 3, len - 3);
}
+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 *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;
+ /* skip response code for now */
+ pdu += 2;
+ len -= 2;
+
+ if (req == NULL || req->opcode != req_opcode) {
+ DBG("Indication received without pending request");
+ goto done;
+ }
+
+ /* TODO: handle response */
+
+ 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)
{
} 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);
}
}
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;
}