The media control client used to subscribe to GMCS characteristics as the characteristics were discovered, in effect queuing ATT requests. Since then, the ATT MTU exchange has become automatic, leading to larger ATT MTUs and more discovered characteristics per ATT response, which again lead to the client enqueuing more subscriptions and running out of buffers, with discovery failing as a result. This commit moves the subscription to GMCS characteristics to a separate sequence, so that it happens serially after discovery, rather than in parallel with the discovery. - Remove separate discovery parameters for the characteristics - they are no longer needed. - Add a subscription callback and a couple of subscription functions The subscription for characteristics for the included OTS has not been modified. Only two of these are subscribed to, and doing that is currently not a problem. And I need to better understand the interaction with OTS and how these subscriptipons are used. Signed-off-by: Asbjørn Sæbø <asbjorn.sabo@nordicsemi.no> Refactor subscription to get chaining into callback
3008 lines
83 KiB
C
3008 lines
83 KiB
C
/** @file
|
|
* @brief Bluetooth Media Control Client/Protocol implementation
|
|
*/
|
|
|
|
/*
|
|
* Copyright (c) 2019 - 2021 Nordic Semiconductor ASA
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#include <zephyr/zephyr.h>
|
|
#include <zephyr/types.h>
|
|
#include <zephyr/sys/byteorder.h>
|
|
#include <zephyr/sys/check.h>
|
|
#include <zephyr/device.h>
|
|
#include <zephyr/init.h>
|
|
|
|
#include <zephyr/bluetooth/bluetooth.h>
|
|
#include <zephyr/bluetooth/conn.h>
|
|
#include <zephyr/bluetooth/gatt.h>
|
|
#include <zephyr/bluetooth/audio/mcc.h>
|
|
|
|
#include <zephyr/bluetooth/services/ots.h>
|
|
#include "../services/ots/ots_client_internal.h"
|
|
|
|
/* TODO: Temporarily copied here from media_proxy_internal.h - clean up */
|
|
/* Debug output of 48 bit Object ID value */
|
|
/* (Zephyr does not yet support debug output of more than 32 bit values.) */
|
|
/* Takes a text and a 64-bit integer as input */
|
|
#define BT_DBG_OBJ_ID(text, id64) \
|
|
do { \
|
|
if (IS_ENABLED(CONFIG_BT_DEBUG_MCS)) { \
|
|
char t[BT_OTS_OBJ_ID_STR_LEN]; \
|
|
(void)bt_ots_obj_id_to_str(id64, t, sizeof(t)); \
|
|
BT_DBG(text "0x%s", t); \
|
|
} \
|
|
} while (0)
|
|
|
|
|
|
#define BT_DBG_ENABLED IS_ENABLED(CONFIG_BT_DEBUG_MCC)
|
|
#define LOG_MODULE_NAME bt_mcc
|
|
#include "common/log.h"
|
|
|
|
struct mcs_instance_t {
|
|
uint16_t start_handle;
|
|
uint16_t end_handle;
|
|
uint16_t player_name_handle;
|
|
#ifdef CONFIG_BT_MCC_OTS
|
|
uint16_t icon_obj_id_handle;
|
|
#endif /* CONFIG_BT_MCC_OTS */
|
|
uint16_t icon_url_handle;
|
|
uint16_t track_changed_handle;
|
|
uint16_t track_title_handle;
|
|
uint16_t track_duration_handle;
|
|
uint16_t track_position_handle;
|
|
uint16_t playback_speed_handle;
|
|
uint16_t seeking_speed_handle;
|
|
#ifdef CONFIG_BT_MCC_OTS
|
|
uint16_t segments_obj_id_handle;
|
|
uint16_t current_track_obj_id_handle;
|
|
uint16_t next_track_obj_id_handle;
|
|
uint16_t current_group_obj_id_handle;
|
|
uint16_t parent_group_obj_id_handle;
|
|
#endif /* CONFIG_BT_MCC_OTS */
|
|
uint16_t playing_order_handle;
|
|
uint16_t playing_orders_supported_handle;
|
|
uint16_t media_state_handle;
|
|
uint16_t cp_handle;
|
|
uint16_t opcodes_supported_handle;
|
|
#ifdef CONFIG_BT_MCC_OTS
|
|
uint16_t scp_handle;
|
|
uint16_t search_results_obj_id_handle;
|
|
#endif /* CONFIG_BT_MCC_OTS */
|
|
uint16_t content_control_id_handle;
|
|
|
|
struct bt_gatt_subscribe_params player_name_sub_params;
|
|
struct bt_gatt_subscribe_params track_changed_sub_params;
|
|
struct bt_gatt_subscribe_params track_title_sub_params;
|
|
struct bt_gatt_subscribe_params track_duration_sub_params;
|
|
struct bt_gatt_subscribe_params track_position_sub_params;
|
|
struct bt_gatt_subscribe_params playback_speed_sub_params;
|
|
struct bt_gatt_subscribe_params seeking_speed_sub_params;
|
|
#ifdef CONFIG_BT_MCC_OTS
|
|
struct bt_gatt_subscribe_params current_track_obj_sub_params;
|
|
struct bt_gatt_subscribe_params next_track_obj_sub_params;
|
|
struct bt_gatt_subscribe_params parent_group_obj_sub_params;
|
|
struct bt_gatt_subscribe_params current_group_obj_sub_params;
|
|
#endif /* CONFIG_BT_MCC_OTS */
|
|
struct bt_gatt_subscribe_params playing_order_sub_params;
|
|
struct bt_gatt_subscribe_params media_state_sub_params;
|
|
struct bt_gatt_subscribe_params cp_sub_params;
|
|
struct bt_gatt_subscribe_params opcodes_supported_sub_params;
|
|
#ifdef CONFIG_BT_MCC_OTS
|
|
struct bt_gatt_subscribe_params scp_sub_params;
|
|
struct bt_gatt_subscribe_params search_results_obj_sub_params;
|
|
#endif /* CONFIG_BT_MCC_OTS */
|
|
|
|
/* The write buffer is used for
|
|
* - track position (4 octets)
|
|
* - playback speed (1 octet)
|
|
* - playing order (1 octet)
|
|
* - the control point (5 octets)
|
|
* (1 octet opcode + optionally 4 octet param)
|
|
* (mpl_cmd.opcode + mpl_cmd.param)
|
|
* If the object transfer client is included, it is also used for
|
|
* - object IDs (6 octets - BT_OTS_OBJ_ID_SIZE) and
|
|
* - the search control point (64 octets - SEARCH_LEN_MAX)
|
|
*
|
|
* If there is no OTC, the largest is control point
|
|
* If OTC is included, the largest is the search control point
|
|
*/
|
|
#ifdef CONFIG_BT_MCC_OTS
|
|
char write_buf[SEARCH_LEN_MAX];
|
|
#else
|
|
/* Trick to be able to use sizeof on members of a struct type */
|
|
/* TODO: Rewrite the mpl_cmd to have the "use_param" parameter */
|
|
/* separately, and the opcode and param alone as a struct */
|
|
char write_buf[sizeof(((struct mpl_cmd *)0)->opcode) +
|
|
sizeof(((struct mpl_cmd *)0)->param)];
|
|
#endif /* CONFIG_BT_MCC_OTS */
|
|
|
|
struct bt_gatt_write_params write_params;
|
|
bool busy;
|
|
|
|
#ifdef CONFIG_BT_MCC_OTS
|
|
struct bt_ots_client otc;
|
|
#endif /* CONFIG_BT_MCC_OTS */
|
|
};
|
|
|
|
static struct bt_gatt_discover_params discover_params;
|
|
static struct bt_gatt_read_params read_params;
|
|
|
|
static struct mcs_instance_t *cur_mcs_inst;
|
|
|
|
static struct mcs_instance_t mcs_inst;
|
|
static struct bt_uuid_16 uuid = BT_UUID_INIT_16(0);
|
|
|
|
static struct bt_mcc_cb *mcc_cb;
|
|
static bool subscribe_all;
|
|
|
|
#ifdef CONFIG_BT_MCC_OTS
|
|
NET_BUF_SIMPLE_DEFINE_STATIC(otc_obj_buf, CONFIG_BT_MCC_OTC_OBJ_BUF_SIZE);
|
|
static struct bt_ots_client_cb otc_cb;
|
|
#endif /* CONFIG_BT_MCC_OTS */
|
|
|
|
|
|
|
|
#ifdef CONFIG_BT_MCC_OTS
|
|
void on_obj_selected(struct bt_ots_client *otc_inst,
|
|
struct bt_conn *conn, int err);
|
|
|
|
void on_object_metadata(struct bt_ots_client *otc_inst,
|
|
struct bt_conn *conn, int err,
|
|
uint8_t metadata_read);
|
|
|
|
int on_icon_content(struct bt_ots_client *otc_inst,
|
|
struct bt_conn *conn, uint32_t offset,
|
|
uint32_t len, uint8_t *data_p, bool is_complete);
|
|
#endif /* CONFIG_BT_MCC_OTS */
|
|
|
|
|
|
static uint8_t mcc_read_player_name_cb(struct bt_conn *conn, uint8_t err,
|
|
struct bt_gatt_read_params *params,
|
|
const void *data, uint16_t length)
|
|
{
|
|
int cb_err = err;
|
|
char name[CONFIG_BT_MCC_MEDIA_PLAYER_NAME_MAX];
|
|
|
|
cur_mcs_inst->busy = false;
|
|
BT_DBG("err: 0x%02x, length: %d, data: %p", err, length, data);
|
|
|
|
if (err) {
|
|
BT_DBG("err: 0x%02x", err);
|
|
} else if (!data) {
|
|
cb_err = BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN);
|
|
} else {
|
|
BT_HEXDUMP_DBG(data, length, "Player name read");
|
|
|
|
if (length >= sizeof(name)) {
|
|
length = sizeof(name) - 1;
|
|
}
|
|
|
|
(void)memcpy(&name, data, length);
|
|
name[length] = '\0';
|
|
BT_DBG("Player name: %s", name);
|
|
}
|
|
|
|
if (mcc_cb && mcc_cb->read_player_name) {
|
|
mcc_cb->read_player_name(conn, cb_err, name);
|
|
}
|
|
|
|
return BT_GATT_ITER_STOP;
|
|
}
|
|
|
|
|
|
#ifdef CONFIG_BT_MCC_OTS
|
|
static uint8_t mcc_read_icon_obj_id_cb(struct bt_conn *conn, uint8_t err,
|
|
struct bt_gatt_read_params *params,
|
|
const void *data, uint16_t length)
|
|
{
|
|
int cb_err = err;
|
|
uint8_t *pid = (uint8_t *)data;
|
|
uint64_t id = 0;
|
|
|
|
cur_mcs_inst->busy = false;
|
|
BT_DBG("err: 0x%02x, length: %d, data: %p", err, length, data);
|
|
if (err) {
|
|
BT_DBG("err: 0x%02x", err);
|
|
} else if ((!pid) || (length != BT_OTS_OBJ_ID_SIZE)) {
|
|
cb_err = BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN);
|
|
} else {
|
|
BT_HEXDUMP_DBG(pid, length, "Icon Object ID");
|
|
id = sys_get_le48(pid);
|
|
BT_DBG_OBJ_ID("Icon Object ID: ", id);
|
|
}
|
|
|
|
if (mcc_cb && mcc_cb->read_icon_obj_id) {
|
|
mcc_cb->read_icon_obj_id(conn, cb_err, id);
|
|
}
|
|
|
|
return BT_GATT_ITER_STOP;
|
|
}
|
|
#endif /* CONFIG_BT_MCC_OTS */
|
|
|
|
static uint8_t mcc_read_icon_url_cb(struct bt_conn *conn, uint8_t err,
|
|
struct bt_gatt_read_params *params,
|
|
const void *data, uint16_t length)
|
|
{
|
|
int cb_err = err;
|
|
char url[CONFIG_BT_MCC_ICON_URL_MAX];
|
|
|
|
cur_mcs_inst->busy = false;
|
|
BT_DBG("err: 0x%02x, length: %d, data: %p", err, length, data);
|
|
if (err) {
|
|
BT_DBG("err: 0x%02x", err);
|
|
} else if (!data) {
|
|
cb_err = BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN);
|
|
} else if (length >= sizeof(url)) {
|
|
cb_err = BT_ATT_ERR_INSUFFICIENT_RESOURCES;
|
|
} else {
|
|
BT_HEXDUMP_DBG(data, length, "Icon URL");
|
|
(void)memcpy(&url, data, length);
|
|
url[length] = '\0';
|
|
BT_DBG("Icon URL: %s", url);
|
|
}
|
|
|
|
if (mcc_cb && mcc_cb->read_icon_url) {
|
|
mcc_cb->read_icon_url(conn, cb_err, url);
|
|
}
|
|
|
|
return BT_GATT_ITER_STOP;
|
|
}
|
|
|
|
static uint8_t mcc_read_track_title_cb(struct bt_conn *conn, uint8_t err,
|
|
struct bt_gatt_read_params *params,
|
|
const void *data, uint16_t length)
|
|
{
|
|
int cb_err = err;
|
|
char title[CONFIG_BT_MCC_TRACK_TITLE_MAX];
|
|
|
|
cur_mcs_inst->busy = false;
|
|
if (err) {
|
|
BT_DBG("err: 0x%02x", err);
|
|
} else if (!data) {
|
|
cb_err = BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN);
|
|
} else {
|
|
BT_HEXDUMP_DBG(data, length, "Track title");
|
|
if (length >= sizeof(title)) {
|
|
/* If the description is too long; clip it. */
|
|
length = sizeof(title) - 1;
|
|
}
|
|
(void)memcpy(&title, data, length);
|
|
title[length] = '\0';
|
|
BT_DBG("Track title: %s", title);
|
|
}
|
|
|
|
if (mcc_cb && mcc_cb->read_track_title) {
|
|
mcc_cb->read_track_title(conn, cb_err, title);
|
|
}
|
|
|
|
return BT_GATT_ITER_STOP;
|
|
}
|
|
|
|
static uint8_t mcc_read_track_duration_cb(struct bt_conn *conn, uint8_t err,
|
|
struct bt_gatt_read_params *params,
|
|
const void *data, uint16_t length)
|
|
{
|
|
int cb_err = err;
|
|
int32_t dur = 0;
|
|
|
|
cur_mcs_inst->busy = false;
|
|
if (err) {
|
|
BT_DBG("err: 0x%02x", err);
|
|
} else if ((!data) || (length != sizeof(dur))) {
|
|
BT_DBG("length: %d, data: %p", length, data);
|
|
cb_err = BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN);
|
|
} else {
|
|
dur = sys_get_le32((uint8_t *)data);
|
|
BT_DBG("Track duration: %d", dur);
|
|
BT_HEXDUMP_DBG(data, sizeof(int32_t), "Track duration");
|
|
}
|
|
|
|
if (mcc_cb && mcc_cb->read_track_duration) {
|
|
mcc_cb->read_track_duration(conn, cb_err, dur);
|
|
}
|
|
|
|
return BT_GATT_ITER_STOP;
|
|
}
|
|
|
|
static uint8_t mcc_read_track_position_cb(struct bt_conn *conn, uint8_t err,
|
|
struct bt_gatt_read_params *params,
|
|
const void *data, uint16_t length)
|
|
{
|
|
int cb_err = err;
|
|
int32_t pos = 0;
|
|
|
|
cur_mcs_inst->busy = false;
|
|
if (err) {
|
|
BT_DBG("err: 0x%02x", err);
|
|
} else if ((!data) || (length != sizeof(pos))) {
|
|
BT_DBG("length: %d, data: %p", length, data);
|
|
cb_err = BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN);
|
|
} else {
|
|
pos = sys_get_le32((uint8_t *)data);
|
|
BT_DBG("Track position: %d", pos);
|
|
BT_HEXDUMP_DBG(data, sizeof(pos), "Track position");
|
|
}
|
|
|
|
if (mcc_cb && mcc_cb->read_track_position) {
|
|
mcc_cb->read_track_position(conn, cb_err, pos);
|
|
}
|
|
|
|
return BT_GATT_ITER_STOP;
|
|
}
|
|
|
|
static void mcs_write_track_position_cb(struct bt_conn *conn, uint8_t err,
|
|
struct bt_gatt_write_params *params)
|
|
{
|
|
int cb_err = err;
|
|
int32_t pos = 0;
|
|
|
|
cur_mcs_inst->busy = false;
|
|
if (err) {
|
|
BT_DBG("err: 0x%02x", err);
|
|
} else if (!params->data || params->length != sizeof(pos)) {
|
|
BT_DBG("length: %d, data: %p", params->length, params->data);
|
|
cb_err = BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN);
|
|
} else {
|
|
pos = sys_get_le32((uint8_t *)params->data);
|
|
BT_DBG("Track position: %d", pos);
|
|
BT_HEXDUMP_DBG(params->data, sizeof(pos),
|
|
"Track position in callback");
|
|
}
|
|
|
|
if (mcc_cb && mcc_cb->set_track_position) {
|
|
mcc_cb->set_track_position(conn, cb_err, pos);
|
|
}
|
|
}
|
|
|
|
static uint8_t mcc_read_playback_speed_cb(struct bt_conn *conn, uint8_t err,
|
|
struct bt_gatt_read_params *params,
|
|
const void *data, uint16_t length)
|
|
{
|
|
int cb_err = err;
|
|
int8_t speed = 0;
|
|
|
|
cur_mcs_inst->busy = false;
|
|
if (err) {
|
|
BT_DBG("err: 0x%02x", err);
|
|
} else if ((!data) || (length != sizeof(speed))) {
|
|
BT_DBG("length: %d, data: %p", length, data);
|
|
cb_err = BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN);
|
|
} else {
|
|
speed = *(int8_t *)data;
|
|
BT_DBG("Playback speed: %d", speed);
|
|
BT_HEXDUMP_DBG(data, sizeof(int8_t), "Playback speed");
|
|
}
|
|
|
|
if (mcc_cb && mcc_cb->read_playback_speed) {
|
|
mcc_cb->read_playback_speed(conn, cb_err, speed);
|
|
}
|
|
|
|
return BT_GATT_ITER_STOP;
|
|
}
|
|
|
|
static void mcs_write_playback_speed_cb(struct bt_conn *conn, uint8_t err,
|
|
struct bt_gatt_write_params *params)
|
|
{
|
|
int cb_err = err;
|
|
int8_t speed = 0;
|
|
|
|
cur_mcs_inst->busy = false;
|
|
if (err) {
|
|
BT_DBG("err: 0x%02x", err);
|
|
} else if (!params->data || params->length != sizeof(speed)) {
|
|
BT_DBG("length: %d, data: %p", params->length, params->data);
|
|
cb_err = BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN);
|
|
} else {
|
|
speed = *(int8_t *)params->data;
|
|
BT_DBG("Playback_speed: %d", speed);
|
|
}
|
|
|
|
if (mcc_cb && mcc_cb->set_playback_speed) {
|
|
mcc_cb->set_playback_speed(conn, cb_err, speed);
|
|
}
|
|
}
|
|
|
|
static uint8_t mcc_read_seeking_speed_cb(struct bt_conn *conn, uint8_t err,
|
|
struct bt_gatt_read_params *params,
|
|
const void *data, uint16_t length)
|
|
{
|
|
int cb_err = err;
|
|
int8_t speed = 0;
|
|
|
|
cur_mcs_inst->busy = false;
|
|
if (err) {
|
|
BT_DBG("err: 0x%02x", err);
|
|
} else if ((!data) || (length != sizeof(speed))) {
|
|
BT_DBG("length: %d, data: %p", length, data);
|
|
cb_err = BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN);
|
|
} else {
|
|
speed = *(int8_t *)data;
|
|
BT_DBG("Seeking speed: %d", speed);
|
|
BT_HEXDUMP_DBG(data, sizeof(int8_t), "Seeking speed");
|
|
}
|
|
|
|
if (mcc_cb && mcc_cb->read_seeking_speed) {
|
|
mcc_cb->read_seeking_speed(conn, cb_err, speed);
|
|
}
|
|
|
|
return BT_GATT_ITER_STOP;
|
|
}
|
|
|
|
#ifdef CONFIG_BT_MCC_OTS
|
|
static uint8_t mcc_read_segments_obj_id_cb(struct bt_conn *conn, uint8_t err,
|
|
struct bt_gatt_read_params *params,
|
|
const void *data, uint16_t length)
|
|
{
|
|
int cb_err;
|
|
uint8_t *pid = (uint8_t *)data;
|
|
uint64_t id = 0;
|
|
|
|
cur_mcs_inst->busy = false;
|
|
if (err) {
|
|
BT_DBG("err: 0x%02x", err);
|
|
cb_err = err;
|
|
} else if ((!pid) || (length != BT_OTS_OBJ_ID_SIZE)) {
|
|
BT_DBG("length: %d, data: %p", length, data);
|
|
cb_err = BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN);
|
|
} else {
|
|
BT_HEXDUMP_DBG(pid, length, "Segments Object ID");
|
|
id = sys_get_le48(pid);
|
|
BT_DBG_OBJ_ID("Segments Object ID: ", id);
|
|
cb_err = 0;
|
|
}
|
|
|
|
if (mcc_cb && mcc_cb->read_segments_obj_id) {
|
|
mcc_cb->read_segments_obj_id(conn, cb_err, id);
|
|
}
|
|
|
|
return BT_GATT_ITER_STOP;
|
|
}
|
|
|
|
static uint8_t mcc_read_current_track_obj_id_cb(struct bt_conn *conn, uint8_t err,
|
|
struct bt_gatt_read_params *params,
|
|
const void *data, uint16_t length)
|
|
{
|
|
int cb_err;
|
|
uint8_t *pid = (uint8_t *)data;
|
|
uint64_t id = 0;
|
|
|
|
cur_mcs_inst->busy = false;
|
|
if (err) {
|
|
BT_DBG("err: 0x%02x", err);
|
|
cb_err = err;
|
|
} else if ((!pid) || (length != BT_OTS_OBJ_ID_SIZE)) {
|
|
BT_DBG("length: %d, data: %p", length, data);
|
|
cb_err = BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN);
|
|
} else {
|
|
BT_HEXDUMP_DBG(pid, length, "Current Track Object ID");
|
|
id = sys_get_le48(pid);
|
|
BT_DBG_OBJ_ID("Current Track Object ID: ", id);
|
|
cb_err = 0;
|
|
}
|
|
|
|
if (mcc_cb && mcc_cb->read_current_track_obj_id) {
|
|
mcc_cb->read_current_track_obj_id(conn, cb_err, id);
|
|
}
|
|
|
|
return BT_GATT_ITER_STOP;
|
|
}
|
|
|
|
static void mcs_write_current_track_obj_id_cb(struct bt_conn *conn, uint8_t err,
|
|
struct bt_gatt_write_params *params)
|
|
{
|
|
int cb_err = err;
|
|
uint64_t obj_id = 0;
|
|
|
|
cur_mcs_inst->busy = false;
|
|
if (err) {
|
|
BT_DBG("err: 0x%02x", err);
|
|
} else if (!params->data || params->length != BT_OTS_OBJ_ID_SIZE) {
|
|
BT_DBG("length: %d, data: %p", params->length, params->data);
|
|
cb_err = BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN);
|
|
} else {
|
|
obj_id = sys_get_le48((const uint8_t *)params->data);
|
|
BT_DBG_OBJ_ID("Object ID: ", obj_id);
|
|
}
|
|
|
|
if (mcc_cb && mcc_cb->set_current_track_obj_id) {
|
|
mcc_cb->set_current_track_obj_id(conn, cb_err, obj_id);
|
|
}
|
|
}
|
|
|
|
static uint8_t mcc_read_next_track_obj_id_cb(struct bt_conn *conn, uint8_t err,
|
|
struct bt_gatt_read_params *params,
|
|
const void *data, uint16_t length)
|
|
{
|
|
int cb_err = err;
|
|
uint8_t *pid = (uint8_t *)data;
|
|
uint64_t id = 0;
|
|
|
|
cur_mcs_inst->busy = false;
|
|
if (err) {
|
|
BT_DBG("err: 0x%02x", err);
|
|
} else if (length == 0) {
|
|
BT_DBG("Characteristic is empty");
|
|
} else if (!pid || (length != BT_OTS_OBJ_ID_SIZE)) {
|
|
BT_DBG("length: %d, data: %p", length, data);
|
|
cb_err = BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN);
|
|
} else {
|
|
BT_HEXDUMP_DBG(pid, length, "Next Track Object ID");
|
|
id = sys_get_le48(pid);
|
|
BT_DBG_OBJ_ID("Next Track Object ID: ", id);
|
|
}
|
|
|
|
if (mcc_cb && mcc_cb->read_next_track_obj_id) {
|
|
mcc_cb->read_next_track_obj_id(conn, cb_err, id);
|
|
}
|
|
|
|
return BT_GATT_ITER_STOP;
|
|
}
|
|
|
|
static void mcs_write_next_track_obj_id_cb(struct bt_conn *conn, uint8_t err,
|
|
struct bt_gatt_write_params *params)
|
|
{
|
|
int cb_err = err;
|
|
uint64_t obj_id = 0;
|
|
|
|
cur_mcs_inst->busy = false;
|
|
if (err) {
|
|
BT_DBG("err: 0x%02x", err);
|
|
} else if (!params->data || params->length != BT_OTS_OBJ_ID_SIZE) {
|
|
BT_DBG("length: %d, data: %p", params->length, params->data);
|
|
cb_err = BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN);
|
|
} else {
|
|
obj_id = sys_get_le48((const uint8_t *)params->data);
|
|
BT_DBG_OBJ_ID("Object ID: ", obj_id);
|
|
}
|
|
|
|
if (mcc_cb && mcc_cb->set_next_track_obj_id) {
|
|
mcc_cb->set_next_track_obj_id(conn, cb_err, obj_id);
|
|
}
|
|
}
|
|
|
|
static uint8_t mcc_read_parent_group_obj_id_cb(struct bt_conn *conn, uint8_t err,
|
|
struct bt_gatt_read_params *params,
|
|
const void *data, uint16_t length)
|
|
{
|
|
int cb_err = err;
|
|
uint8_t *pid = (uint8_t *)data;
|
|
uint64_t id = 0;
|
|
|
|
cur_mcs_inst->busy = false;
|
|
if (err) {
|
|
BT_DBG("err: 0x%02x", err);
|
|
} else if (!pid || (length != BT_OTS_OBJ_ID_SIZE)) {
|
|
BT_DBG("length: %d, data: %p", length, data);
|
|
cb_err = BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN);
|
|
} else {
|
|
BT_HEXDUMP_DBG(pid, length, "Parent Group Object ID");
|
|
id = sys_get_le48(pid);
|
|
BT_DBG_OBJ_ID("Parent Group Object ID: ", id);
|
|
}
|
|
|
|
if (mcc_cb && mcc_cb->read_parent_group_obj_id) {
|
|
mcc_cb->read_parent_group_obj_id(conn, cb_err, id);
|
|
}
|
|
|
|
return BT_GATT_ITER_STOP;
|
|
}
|
|
|
|
static uint8_t mcc_read_current_group_obj_id_cb(struct bt_conn *conn, uint8_t err,
|
|
struct bt_gatt_read_params *params,
|
|
const void *data, uint16_t length)
|
|
{
|
|
int cb_err = err;
|
|
uint8_t *pid = (uint8_t *)data;
|
|
uint64_t id = 0;
|
|
|
|
cur_mcs_inst->busy = false;
|
|
if (err) {
|
|
BT_DBG("err: 0x%02x", err);
|
|
} else if (!pid || (length != BT_OTS_OBJ_ID_SIZE)) {
|
|
BT_DBG("length: %d, data: %p", length, data);
|
|
cb_err = BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN);
|
|
} else {
|
|
BT_HEXDUMP_DBG(pid, length, "Current Group Object ID");
|
|
id = sys_get_le48(pid);
|
|
BT_DBG_OBJ_ID("Current Group Object ID: ", id);
|
|
}
|
|
|
|
if (mcc_cb && mcc_cb->read_current_group_obj_id) {
|
|
mcc_cb->read_current_group_obj_id(conn, cb_err, id);
|
|
}
|
|
|
|
return BT_GATT_ITER_STOP;
|
|
}
|
|
|
|
static void mcs_write_current_group_obj_id_cb(struct bt_conn *conn, uint8_t err,
|
|
struct bt_gatt_write_params *params)
|
|
{
|
|
int cb_err = err;
|
|
uint64_t obj_id = 0;
|
|
|
|
cur_mcs_inst->busy = false;
|
|
if (err) {
|
|
BT_DBG("err: 0x%02x", err);
|
|
} else if (!params->data || params->length != BT_OTS_OBJ_ID_SIZE) {
|
|
BT_DBG("length: %d, data: %p", params->length, params->data);
|
|
cb_err = BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN);
|
|
} else {
|
|
obj_id = sys_get_le48((const uint8_t *)params->data);
|
|
BT_DBG_OBJ_ID("Object ID: ", obj_id);
|
|
}
|
|
|
|
if (mcc_cb && mcc_cb->set_current_group_obj_id) {
|
|
mcc_cb->set_current_group_obj_id(conn, cb_err, obj_id);
|
|
}
|
|
}
|
|
#endif /* CONFIG_BT_MCC_OTS */
|
|
|
|
static uint8_t mcc_read_playing_order_cb(struct bt_conn *conn, uint8_t err,
|
|
struct bt_gatt_read_params *params,
|
|
const void *data, uint16_t length)
|
|
{
|
|
int cb_err = err;
|
|
uint8_t order = 0;
|
|
|
|
cur_mcs_inst->busy = false;
|
|
if (err) {
|
|
BT_DBG("err: 0x%02x", err);
|
|
} else if ((!data) || (length != sizeof(order))) {
|
|
BT_DBG("length: %d, data: %p", length, data);
|
|
cb_err = BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN);
|
|
} else {
|
|
order = *(uint8_t *)data;
|
|
BT_DBG("Playing order: %d", order);
|
|
BT_HEXDUMP_DBG(data, sizeof(order), "Playing order");
|
|
}
|
|
|
|
if (mcc_cb && mcc_cb->read_playing_order) {
|
|
mcc_cb->read_playing_order(conn, cb_err, order);
|
|
}
|
|
|
|
return BT_GATT_ITER_STOP;
|
|
}
|
|
|
|
static void mcs_write_playing_order_cb(struct bt_conn *conn, uint8_t err,
|
|
struct bt_gatt_write_params *params)
|
|
{
|
|
int cb_err = err;
|
|
uint8_t order = 0;
|
|
|
|
cur_mcs_inst->busy = false;
|
|
if (err) {
|
|
BT_DBG("err: 0x%02x", err);
|
|
} else if (!params->data || params->length != sizeof(order)) {
|
|
BT_DBG("length: %d, data: %p", params->length, params->data);
|
|
cb_err = BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN);
|
|
} else {
|
|
order = *(uint8_t *)params->data;
|
|
BT_DBG("Playing order: %d", *(uint8_t *)params->data);
|
|
}
|
|
|
|
if (mcc_cb && mcc_cb->set_playing_order) {
|
|
mcc_cb->set_playing_order(conn, cb_err, order);
|
|
}
|
|
}
|
|
|
|
static uint8_t mcc_read_playing_orders_supported_cb(struct bt_conn *conn, uint8_t err,
|
|
struct bt_gatt_read_params *params,
|
|
const void *data, uint16_t length)
|
|
{
|
|
int cb_err = err;
|
|
uint16_t orders = 0;
|
|
|
|
cur_mcs_inst->busy = false;
|
|
if (err) {
|
|
BT_DBG("err: 0x%02x", err);
|
|
} else if (!data || length != sizeof(orders)) {
|
|
BT_DBG("length: %d, data: %p", length, data);
|
|
cb_err = BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN);
|
|
} else {
|
|
orders = sys_get_le16((uint8_t *)data);
|
|
BT_DBG("Playing orders_supported: %d", orders);
|
|
BT_HEXDUMP_DBG(data, sizeof(orders), "Playing orders supported");
|
|
}
|
|
|
|
if (mcc_cb && mcc_cb->read_playing_orders_supported) {
|
|
mcc_cb->read_playing_orders_supported(conn, cb_err, orders);
|
|
}
|
|
|
|
return BT_GATT_ITER_STOP;
|
|
}
|
|
|
|
static uint8_t mcc_read_media_state_cb(struct bt_conn *conn, uint8_t err,
|
|
struct bt_gatt_read_params *params,
|
|
const void *data, uint16_t length)
|
|
{
|
|
int cb_err = err;
|
|
uint8_t state = 0;
|
|
|
|
cur_mcs_inst->busy = false;
|
|
if (err) {
|
|
BT_DBG("err: 0x%02x", err);
|
|
} else if (!data || length != sizeof(state)) {
|
|
BT_DBG("length: %d, data: %p", length, data);
|
|
cb_err = BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN);
|
|
} else {
|
|
state = *(uint8_t *)data;
|
|
BT_DBG("Media state: %d", state);
|
|
BT_HEXDUMP_DBG(data, sizeof(uint8_t), "Media state");
|
|
}
|
|
|
|
if (mcc_cb && mcc_cb->read_media_state) {
|
|
mcc_cb->read_media_state(conn, cb_err, state);
|
|
}
|
|
|
|
return BT_GATT_ITER_STOP;
|
|
}
|
|
|
|
static void mcs_write_cp_cb(struct bt_conn *conn, uint8_t err,
|
|
struct bt_gatt_write_params *params)
|
|
{
|
|
int cb_err = err;
|
|
struct mpl_cmd cmd = {0};
|
|
|
|
cur_mcs_inst->busy = false;
|
|
|
|
if (err) {
|
|
BT_DBG("err: 0x%02x", err);
|
|
} else if (!params->data ||
|
|
(params->length != sizeof(cmd.opcode) &&
|
|
params->length != sizeof(cmd.opcode) + sizeof(cmd.param))) {
|
|
/* Above: No data pointer, or length not equal to any of the */
|
|
/* two possible values */
|
|
BT_DBG("length: %d, data: %p", params->length, params->data);
|
|
cb_err = BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN);
|
|
} else {
|
|
(void)memcpy(&cmd.opcode, params->data, sizeof(cmd.opcode));
|
|
if (params->length == sizeof(cmd.opcode) + sizeof(cmd.param)) {
|
|
(void)memcpy(&cmd.param,
|
|
(char *)(params->data) + sizeof(cmd.opcode),
|
|
sizeof(cmd.param));
|
|
cmd.use_param = true;
|
|
BT_DBG("Command in callback: %d, param: %d", cmd.opcode, cmd.param);
|
|
}
|
|
}
|
|
|
|
if (mcc_cb && mcc_cb->send_cmd) {
|
|
mcc_cb->send_cmd(conn, cb_err, &cmd);
|
|
}
|
|
}
|
|
|
|
static uint8_t mcc_read_opcodes_supported_cb(struct bt_conn *conn, uint8_t err,
|
|
struct bt_gatt_read_params *params,
|
|
const void *data, uint16_t length)
|
|
{
|
|
int cb_err = err;
|
|
int32_t operations = 0;
|
|
|
|
cur_mcs_inst->busy = false;
|
|
|
|
if (err) {
|
|
BT_DBG("err: 0x%02x", err);
|
|
} else if ((!data) || (length != sizeof(operations))) {
|
|
BT_DBG("length: %d, data: %p", length, data);
|
|
cb_err = BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN);
|
|
|
|
} else {
|
|
operations = sys_get_le32((uint8_t *)data);
|
|
BT_DBG("Opcodes supported: %d", operations);
|
|
BT_HEXDUMP_DBG(data, sizeof(operations), "Opcodes_supported");
|
|
}
|
|
|
|
if (mcc_cb && mcc_cb->read_opcodes_supported) {
|
|
mcc_cb->read_opcodes_supported(conn, cb_err, operations);
|
|
}
|
|
|
|
return BT_GATT_ITER_STOP;
|
|
}
|
|
|
|
#ifdef CONFIG_BT_MCC_OTS
|
|
static void mcs_write_scp_cb(struct bt_conn *conn, uint8_t err,
|
|
struct bt_gatt_write_params *params)
|
|
{
|
|
int cb_err = err;
|
|
struct mpl_search search = {0};
|
|
|
|
cur_mcs_inst->busy = false;
|
|
|
|
if (err) {
|
|
BT_DBG("err: 0x%02x", err);
|
|
} else if (!params->data ||
|
|
(params->length > SEARCH_LEN_MAX)) {
|
|
BT_DBG("length: %d, data: %p", params->length, params->data);
|
|
cb_err = BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN);
|
|
} else {
|
|
search.len = params->length;
|
|
(void)memcpy(search.search, params->data, params->length);
|
|
BT_DBG("Length of returned value in callback: %d", search.len);
|
|
}
|
|
|
|
if (mcc_cb && mcc_cb->send_search) {
|
|
mcc_cb->send_search(conn, cb_err, &search);
|
|
}
|
|
}
|
|
|
|
static uint8_t mcc_read_search_results_obj_id_cb(struct bt_conn *conn, uint8_t err,
|
|
struct bt_gatt_read_params *params,
|
|
const void *data, uint16_t length)
|
|
{
|
|
int cb_err = err;
|
|
uint8_t *pid = (uint8_t *)data;
|
|
uint64_t id = 0;
|
|
|
|
cur_mcs_inst->busy = false;
|
|
if (err) {
|
|
BT_DBG("err: 0x%02x", err);
|
|
} else if (length == 0) {
|
|
/* OK - this characteristic may be zero length */
|
|
/* cb_err and id already have correct values */
|
|
BT_DBG("Zero-length Search Results Object ID");
|
|
} else if (!pid || (length != BT_OTS_OBJ_ID_SIZE)) {
|
|
BT_DBG("length: %d, pid: %p", length, pid);
|
|
cb_err = BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN);
|
|
} else {
|
|
id = sys_get_le48(pid);
|
|
BT_DBG_OBJ_ID("Search Results Object ID: ", id);
|
|
}
|
|
|
|
if (mcc_cb && mcc_cb->read_search_results_obj_id) {
|
|
mcc_cb->read_search_results_obj_id(conn, cb_err, id);
|
|
}
|
|
|
|
return BT_GATT_ITER_STOP;
|
|
}
|
|
#endif /* CONFIG_BT_MCC_OTS */
|
|
|
|
static uint8_t mcc_read_content_control_id_cb(struct bt_conn *conn, uint8_t err,
|
|
struct bt_gatt_read_params *params,
|
|
const void *data, uint16_t length)
|
|
{
|
|
int cb_err = err;
|
|
uint8_t ccid = 0;
|
|
|
|
cur_mcs_inst->busy = false;
|
|
|
|
if (err) {
|
|
BT_DBG("err: 0x%02x", err);
|
|
} else if ((!data) || (length != sizeof(ccid))) {
|
|
BT_DBG("length: %d, data: %p", length, data);
|
|
cb_err = BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN);
|
|
|
|
} else {
|
|
ccid = *(uint8_t *)data;
|
|
BT_DBG("Content control ID: %d", ccid);
|
|
}
|
|
|
|
if (mcc_cb && mcc_cb->read_content_control_id) {
|
|
mcc_cb->read_content_control_id(conn, cb_err, ccid);
|
|
}
|
|
|
|
return BT_GATT_ITER_STOP;
|
|
}
|
|
|
|
static uint8_t mcs_notify_handler(struct bt_conn *conn,
|
|
struct bt_gatt_subscribe_params *params,
|
|
const void *data, uint16_t length)
|
|
{
|
|
uint16_t handle = params->value_handle;
|
|
|
|
if (conn == NULL) {
|
|
return BT_GATT_ITER_CONTINUE;
|
|
}
|
|
|
|
BT_DBG("Notification, handle: %d", handle);
|
|
|
|
if (data) {
|
|
if (handle == cur_mcs_inst->player_name_handle) {
|
|
BT_DBG("Player Name notification");
|
|
mcc_read_player_name_cb(conn, 0, NULL, data, length);
|
|
|
|
} else if (handle == cur_mcs_inst->track_changed_handle) {
|
|
/* The Track Changed characteristic can only be */
|
|
/* notified, so that is handled directly here */
|
|
|
|
int cb_err = 0;
|
|
|
|
BT_DBG("Track Changed notification");
|
|
BT_DBG("data: %p, length: %u", data, length);
|
|
|
|
if (length != 0) {
|
|
BT_DBG("Non-zero length: %u", length);
|
|
cb_err = BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN);
|
|
}
|
|
|
|
if (mcc_cb && mcc_cb->track_changed_ntf) {
|
|
mcc_cb->track_changed_ntf(conn, cb_err);
|
|
}
|
|
|
|
} else if (handle == cur_mcs_inst->track_title_handle) {
|
|
BT_DBG("Track Title notification");
|
|
mcc_read_track_title_cb(conn, 0, NULL, data, length);
|
|
|
|
} else if (handle == cur_mcs_inst->track_duration_handle) {
|
|
BT_DBG("Track Duration notification");
|
|
mcc_read_track_duration_cb(conn, 0, NULL, data, length);
|
|
|
|
} else if (handle == cur_mcs_inst->track_position_handle) {
|
|
BT_DBG("Track Position notification");
|
|
mcc_read_track_position_cb(conn, 0, NULL, data, length);
|
|
|
|
} else if (handle == cur_mcs_inst->playback_speed_handle) {
|
|
BT_DBG("Playback Speed notification");
|
|
mcc_read_playback_speed_cb(conn, 0, NULL, data, length);
|
|
|
|
} else if (handle == cur_mcs_inst->seeking_speed_handle) {
|
|
BT_DBG("Seeking Speed notification");
|
|
mcc_read_seeking_speed_cb(conn, 0, NULL, data, length);
|
|
|
|
#ifdef CONFIG_BT_MCC_OTS
|
|
} else if (handle == cur_mcs_inst->current_track_obj_id_handle) {
|
|
BT_DBG("Current Track notification");
|
|
mcc_read_current_track_obj_id_cb(conn, 0, NULL, data,
|
|
length);
|
|
|
|
} else if (handle == cur_mcs_inst->next_track_obj_id_handle) {
|
|
BT_DBG("Next Track notification");
|
|
mcc_read_next_track_obj_id_cb(conn, 0, NULL, data,
|
|
length);
|
|
|
|
} else if (handle == cur_mcs_inst->parent_group_obj_id_handle) {
|
|
BT_DBG("Parent Group notification");
|
|
mcc_read_parent_group_obj_id_cb(conn, 0, NULL, data,
|
|
length);
|
|
|
|
} else if (handle == cur_mcs_inst->current_group_obj_id_handle) {
|
|
BT_DBG("Current Group notification");
|
|
mcc_read_current_group_obj_id_cb(conn, 0, NULL, data,
|
|
length);
|
|
#endif /* CONFIG_BT_MCC_OTS */
|
|
|
|
} else if (handle == cur_mcs_inst->playing_order_handle) {
|
|
BT_DBG("Playing Order notification");
|
|
mcc_read_playing_order_cb(conn, 0, NULL, data, length);
|
|
|
|
} else if (handle == cur_mcs_inst->media_state_handle) {
|
|
BT_DBG("Media State notification");
|
|
mcc_read_media_state_cb(conn, 0, NULL, data, length);
|
|
|
|
} else if (handle == cur_mcs_inst->cp_handle) {
|
|
/* The control point is is a special case - only */
|
|
/* writable and notifiable. Handle directly here. */
|
|
|
|
int cb_err = 0;
|
|
struct mpl_cmd_ntf ntf = {0};
|
|
|
|
BT_DBG("Control Point notification");
|
|
if (length == sizeof(ntf.requested_opcode) + sizeof(ntf.result_code)) {
|
|
ntf.requested_opcode = *(uint8_t *)data;
|
|
ntf.result_code = *((uint8_t *)data +
|
|
sizeof(ntf.requested_opcode));
|
|
BT_DBG("Command: %d, result: %d",
|
|
ntf.requested_opcode, ntf.result_code);
|
|
} else {
|
|
BT_DBG("length: %d, data: %p", length, data);
|
|
cb_err = BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN);
|
|
}
|
|
|
|
if (mcc_cb && mcc_cb->cmd_ntf) {
|
|
mcc_cb->cmd_ntf(conn, cb_err, &ntf);
|
|
}
|
|
|
|
} else if (handle == cur_mcs_inst->opcodes_supported_handle) {
|
|
BT_DBG("Opcodes Supported notification");
|
|
mcc_read_opcodes_supported_cb(conn, 0, NULL, data,
|
|
length);
|
|
|
|
#ifdef CONFIG_BT_MCC_OTS
|
|
} else if (handle == cur_mcs_inst->scp_handle) {
|
|
/* The search control point is a special case - only */
|
|
/* writable and notifiable. Handle directly here. */
|
|
|
|
int cb_err = 0;
|
|
uint8_t result_code = 0;
|
|
|
|
BT_DBG("Search Control Point notification");
|
|
if (length == sizeof(result_code)) {
|
|
result_code = *(uint8_t *)data;
|
|
BT_DBG("Result: %d", result_code);
|
|
} else {
|
|
BT_DBG("length: %d, data: %p", length, data);
|
|
cb_err = BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN);
|
|
}
|
|
|
|
if (mcc_cb && mcc_cb->search_ntf) {
|
|
mcc_cb->search_ntf(conn, cb_err, result_code);
|
|
}
|
|
|
|
} else if (handle == cur_mcs_inst->search_results_obj_id_handle) {
|
|
BT_DBG("Search Results notification");
|
|
mcc_read_search_results_obj_id_cb(conn, 0, NULL, data,
|
|
length);
|
|
#endif /* CONFIG_BT_MCC_OTS */
|
|
|
|
} else {
|
|
BT_DBG("Unknown handle: %d (0x%04X)", handle, handle);
|
|
}
|
|
}
|
|
return BT_GATT_ITER_CONTINUE;
|
|
}
|
|
|
|
static void discovery_failure(struct bt_conn *conn, int err)
|
|
{
|
|
cur_mcs_inst = NULL;
|
|
if (mcc_cb && mcc_cb->discover_mcs) {
|
|
mcc_cb->discover_mcs(conn, err);
|
|
}
|
|
}
|
|
|
|
#ifdef CONFIG_BT_MCC_OTS
|
|
static uint8_t discover_otc_char_func(struct bt_conn *conn,
|
|
const struct bt_gatt_attr *attr,
|
|
struct bt_gatt_discover_params *params)
|
|
{
|
|
int err = 0;
|
|
struct bt_gatt_chrc *chrc;
|
|
struct bt_gatt_subscribe_params *sub_params = NULL;
|
|
|
|
if (attr) {
|
|
/* Found an attribute */
|
|
BT_DBG("[ATTRIBUTE] handle 0x%04X", attr->handle);
|
|
|
|
if (params->type != BT_GATT_DISCOVER_CHARACTERISTIC) {
|
|
/* But it was not a characteristic - continue search */
|
|
return BT_GATT_ITER_CONTINUE;
|
|
}
|
|
|
|
/* We have found an attribute, and it is a characteristic */
|
|
/* Find out which attribute, and subscribe if we should */
|
|
chrc = (struct bt_gatt_chrc *)attr->user_data;
|
|
if (!bt_uuid_cmp(chrc->uuid, BT_UUID_OTS_FEATURE)) {
|
|
BT_DBG("OTS Features");
|
|
cur_mcs_inst->otc.feature_handle = chrc->value_handle;
|
|
} else if (!bt_uuid_cmp(chrc->uuid, BT_UUID_OTS_NAME)) {
|
|
BT_DBG("Object Name");
|
|
cur_mcs_inst->otc.obj_name_handle = chrc->value_handle;
|
|
} else if (!bt_uuid_cmp(chrc->uuid, BT_UUID_OTS_TYPE)) {
|
|
BT_DBG("Object Type");
|
|
cur_mcs_inst->otc.obj_type_handle = chrc->value_handle;
|
|
} else if (!bt_uuid_cmp(chrc->uuid, BT_UUID_OTS_SIZE)) {
|
|
BT_DBG("Object Size");
|
|
cur_mcs_inst->otc.obj_size_handle = chrc->value_handle;
|
|
} else if (!bt_uuid_cmp(chrc->uuid, BT_UUID_OTS_ID)) {
|
|
BT_DBG("Object ID");
|
|
cur_mcs_inst->otc.obj_id_handle = chrc->value_handle;
|
|
} else if (!bt_uuid_cmp(chrc->uuid, BT_UUID_OTS_PROPERTIES)) {
|
|
BT_DBG("Object properties %d", chrc->value_handle);
|
|
cur_mcs_inst->otc.obj_properties_handle = chrc->value_handle;
|
|
} else if (!bt_uuid_cmp(chrc->uuid, BT_UUID_OTS_ACTION_CP)) {
|
|
BT_DBG("Object Action Control Point");
|
|
cur_mcs_inst->otc.oacp_handle = chrc->value_handle;
|
|
sub_params = &cur_mcs_inst->otc.oacp_sub_params;
|
|
sub_params->disc_params = &cur_mcs_inst->otc.oacp_sub_disc_params;
|
|
} else if (!bt_uuid_cmp(chrc->uuid, BT_UUID_OTS_LIST_CP)) {
|
|
BT_DBG("Object List Control Point");
|
|
cur_mcs_inst->otc.olcp_handle = chrc->value_handle;
|
|
sub_params = &cur_mcs_inst->otc.olcp_sub_params;
|
|
sub_params->disc_params = &cur_mcs_inst->otc.olcp_sub_disc_params;
|
|
}
|
|
|
|
if (sub_params) {
|
|
/* With ccc_handle == 0 it will use auto discovery */
|
|
sub_params->ccc_handle = 0;
|
|
sub_params->end_handle = cur_mcs_inst->otc.end_handle;
|
|
sub_params->value = BT_GATT_CCC_INDICATE;
|
|
sub_params->value_handle = chrc->value_handle;
|
|
sub_params->notify = bt_ots_client_indicate_handler;
|
|
|
|
bt_gatt_subscribe(conn, sub_params);
|
|
}
|
|
|
|
return BT_GATT_ITER_CONTINUE;
|
|
}
|
|
|
|
/* No more attributes found */
|
|
cur_mcs_inst->otc.cb = &otc_cb;
|
|
bt_ots_client_register(&cur_mcs_inst->otc);
|
|
|
|
BT_DBG("Setup complete for included OTS");
|
|
(void)memset(params, 0, sizeof(*params));
|
|
|
|
if (mcc_cb && mcc_cb->discover_mcs) {
|
|
mcc_cb->discover_mcs(conn, err);
|
|
}
|
|
|
|
return BT_GATT_ITER_STOP;
|
|
}
|
|
#endif /* CONFIG_BT_MCC_OTS */
|
|
|
|
#ifdef CONFIG_BT_MCC_OTS
|
|
/* This function is called when an included service is found.
|
|
* The function will store the start and end handle for the service,
|
|
* and continue the search for more instances of included services.
|
|
* Lastly, it will start discovery of OTS characteristics.
|
|
*/
|
|
|
|
static uint8_t discover_include_func(struct bt_conn *conn,
|
|
const struct bt_gatt_attr *attr,
|
|
struct bt_gatt_discover_params *params)
|
|
{
|
|
struct bt_gatt_include *include;
|
|
int err = 0;
|
|
|
|
if (attr) {
|
|
BT_DBG("[ATTRIBUTE] handle 0x%04X", attr->handle);
|
|
|
|
__ASSERT(params->type == BT_GATT_DISCOVER_INCLUDE,
|
|
"Wrong type");
|
|
|
|
/* We have found an included service */
|
|
include = (struct bt_gatt_include *)attr->user_data;
|
|
BT_DBG("Include UUID %s", bt_uuid_str(include->uuid));
|
|
|
|
if (bt_uuid_cmp(include->uuid, BT_UUID_OTS)) {
|
|
/* But it is not OTS - continue search */
|
|
BT_WARN("Included service is not OTS");
|
|
return BT_GATT_ITER_CONTINUE;
|
|
}
|
|
|
|
/* We have the included OTS service (MCS includes only one) */
|
|
BT_DBG("Discover include complete for GMCS: OTS");
|
|
cur_mcs_inst->otc.start_handle = include->start_handle;
|
|
cur_mcs_inst->otc.end_handle = include->end_handle;
|
|
(void)memset(params, 0, sizeof(*params));
|
|
|
|
/* Discover characteristics of the included OTS */
|
|
discover_params.start_handle = cur_mcs_inst->otc.start_handle;
|
|
discover_params.end_handle = cur_mcs_inst->otc.end_handle;
|
|
discover_params.type = BT_GATT_DISCOVER_CHARACTERISTIC;
|
|
discover_params.func = discover_otc_char_func;
|
|
|
|
BT_DBG("Start discovery of OTS characteristics");
|
|
err = bt_gatt_discover(conn, &discover_params);
|
|
if (err) {
|
|
BT_DBG("Discovery of OTS chars. failed");
|
|
discovery_failure(conn, err);
|
|
}
|
|
return BT_GATT_ITER_STOP;
|
|
}
|
|
|
|
BT_DBG("No included OTS found");
|
|
/* This is OK, the server may not support OTS. But in that case,
|
|
* discovery stops here.
|
|
*/
|
|
if (mcc_cb && mcc_cb->discover_mcs) {
|
|
mcc_cb->discover_mcs(conn, err);
|
|
}
|
|
return BT_GATT_ITER_STOP;
|
|
}
|
|
|
|
/* Start discovery of included services */
|
|
static void discover_included(struct bt_conn *conn)
|
|
{
|
|
int err;
|
|
|
|
discover_params.start_handle = cur_mcs_inst->start_handle;
|
|
discover_params.end_handle = cur_mcs_inst->end_handle;
|
|
discover_params.type = BT_GATT_DISCOVER_INCLUDE;
|
|
discover_params.func = discover_include_func;
|
|
|
|
BT_DBG("Start discovery of included services");
|
|
err = bt_gatt_discover(conn, &discover_params);
|
|
if (err) {
|
|
BT_DBG("Discovery of included service failed: %d", err);
|
|
discovery_failure(conn, err);
|
|
}
|
|
}
|
|
#endif /* CONFIG_BT_MCC_OTS */
|
|
|
|
static bool subscribe_next_mcs_char(struct bt_conn *conn);
|
|
|
|
/* This function will subscribe to GMCS CCCDs.
|
|
* After this, the function will start discovery of included services.
|
|
*/
|
|
static void subscribe_mcs_char_func(struct bt_conn *conn, uint8_t err,
|
|
struct bt_gatt_subscribe_params *params)
|
|
{
|
|
bool subscription_done;
|
|
|
|
if (err) {
|
|
BT_DBG("Subscription callback error: %u", err);
|
|
discovery_failure(conn, err);
|
|
return;
|
|
}
|
|
|
|
BT_DBG("Subscribed: value handle: %d, ccc handle: %d",
|
|
params->value_handle, params->ccc_handle);
|
|
|
|
/* Subscribe to next characteristic */
|
|
subscription_done = subscribe_next_mcs_char(conn);
|
|
|
|
if (subscription_done) {
|
|
#ifdef CONFIG_BT_MCC_OTS
|
|
/* Start discovery of included services to find OTS */
|
|
discover_included(conn);
|
|
#else
|
|
/* If OTS is not configured, discovery ends here */
|
|
if (mcc_cb && mcc_cb->discover_mcs) {
|
|
mcc_cb->discover_mcs(conn, 0);
|
|
}
|
|
#endif /* CONFIG_BT_MCC_OTS */
|
|
}
|
|
}
|
|
|
|
/* Subscribe to a characteristic - helper function */
|
|
static int do_subscribe(struct bt_conn *conn, uint16_t handle,
|
|
struct bt_gatt_subscribe_params *sub_params)
|
|
{
|
|
/* With ccc_handle == 0 it will use auto discovery */
|
|
sub_params->ccc_handle = 0;
|
|
sub_params->end_handle = cur_mcs_inst->end_handle;
|
|
sub_params->value = BT_GATT_CCC_NOTIFY;
|
|
sub_params->value_handle = handle;
|
|
sub_params->notify = mcs_notify_handler;
|
|
sub_params->subscribe = subscribe_mcs_char_func;
|
|
/* disc_params pointer is also used as subscription flag */
|
|
sub_params->disc_params = &discover_params;
|
|
|
|
BT_DBG("Subscring to handle %d", handle);
|
|
return bt_gatt_subscribe(conn, sub_params);
|
|
}
|
|
|
|
/* Subscribe to the next GMCS CCCD.
|
|
* @return true if there are no more characteristics to subscribe to
|
|
*/
|
|
static bool subscribe_next_mcs_char(struct bt_conn *conn)
|
|
{
|
|
struct bt_gatt_subscribe_params *sub_params = NULL;
|
|
int err = 0;
|
|
|
|
/* The characteristics may be in any order on the server, and
|
|
* not all of them may exist => need to check all.
|
|
* For each of the subscribable characteristics
|
|
* - check if we have a handle for it
|
|
* - check sub_params.disc_params pointer to see if we have
|
|
* already subscribed to it (set in do_subscribe() ).
|
|
*/
|
|
|
|
if (cur_mcs_inst->player_name_handle &&
|
|
cur_mcs_inst->player_name_sub_params.disc_params == NULL) {
|
|
sub_params = &cur_mcs_inst->player_name_sub_params;
|
|
err = do_subscribe(conn, cur_mcs_inst->player_name_handle, sub_params);
|
|
if (err) {
|
|
BT_DBG("Could not subscribe: %d", err);
|
|
discovery_failure(conn, err);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
if (cur_mcs_inst->track_changed_handle &&
|
|
cur_mcs_inst->track_changed_sub_params.disc_params == NULL) {
|
|
sub_params = &cur_mcs_inst->track_changed_sub_params;
|
|
err = do_subscribe(conn, cur_mcs_inst->track_changed_handle, sub_params);
|
|
if (err) {
|
|
BT_DBG("Could not subscribe: %d", err);
|
|
discovery_failure(conn, err);
|
|
}
|
|
return false;
|
|
}
|
|
if (cur_mcs_inst->track_title_handle &&
|
|
cur_mcs_inst->track_title_sub_params.disc_params == NULL) {
|
|
sub_params = &cur_mcs_inst->track_title_sub_params;
|
|
err = do_subscribe(conn, cur_mcs_inst->track_title_handle, sub_params);
|
|
if (err) {
|
|
BT_DBG("Could not subscribe: %d", err);
|
|
discovery_failure(conn, err);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
if (cur_mcs_inst->track_duration_handle &&
|
|
cur_mcs_inst->track_duration_sub_params.disc_params == NULL) {
|
|
sub_params = &cur_mcs_inst->track_duration_sub_params;
|
|
err = do_subscribe(conn, cur_mcs_inst->track_duration_handle, sub_params);
|
|
if (err) {
|
|
BT_DBG("Could not subscribe: %d", err);
|
|
discovery_failure(conn, err);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
if (cur_mcs_inst->track_position_handle &&
|
|
cur_mcs_inst->track_position_sub_params.disc_params == NULL) {
|
|
sub_params = &cur_mcs_inst->track_position_sub_params;
|
|
err = do_subscribe(conn, cur_mcs_inst->track_position_handle, sub_params);
|
|
if (err) {
|
|
BT_DBG("Could not subscribe: %d", err);
|
|
discovery_failure(conn, err);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
if (cur_mcs_inst->playback_speed_handle &&
|
|
cur_mcs_inst->playback_speed_sub_params.disc_params == NULL) {
|
|
sub_params = &cur_mcs_inst->playback_speed_sub_params;
|
|
err = do_subscribe(conn, cur_mcs_inst->playback_speed_handle, sub_params);
|
|
if (err) {
|
|
BT_DBG("Could not subscribe: %d", err);
|
|
discovery_failure(conn, err);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
if (cur_mcs_inst->seeking_speed_handle &&
|
|
cur_mcs_inst->seeking_speed_sub_params.disc_params == NULL) {
|
|
sub_params = &cur_mcs_inst->seeking_speed_sub_params;
|
|
err = do_subscribe(conn, cur_mcs_inst->seeking_speed_handle, sub_params);
|
|
if (err) {
|
|
BT_DBG("Could not subscribe: %d", err);
|
|
discovery_failure(conn, err);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
#ifdef CONFIG_BT_MCC_OTS
|
|
if (cur_mcs_inst->current_track_obj_id_handle &&
|
|
cur_mcs_inst->current_track_obj_sub_params.disc_params == NULL) {
|
|
sub_params = &cur_mcs_inst->current_track_obj_sub_params;
|
|
err = do_subscribe(conn, cur_mcs_inst->current_track_obj_id_handle, sub_params);
|
|
if (err) {
|
|
BT_DBG("Could not subscribe: %d", err);
|
|
discovery_failure(conn, err);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
if (cur_mcs_inst->next_track_obj_id_handle &&
|
|
cur_mcs_inst->next_track_obj_sub_params.disc_params == NULL) {
|
|
sub_params = &cur_mcs_inst->next_track_obj_sub_params;
|
|
err = do_subscribe(conn, cur_mcs_inst->next_track_obj_id_handle, sub_params);
|
|
if (err) {
|
|
BT_DBG("Could not subscribe: %d", err);
|
|
discovery_failure(conn, err);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
if (cur_mcs_inst->parent_group_obj_id_handle &&
|
|
cur_mcs_inst->parent_group_obj_sub_params.disc_params == NULL) {
|
|
sub_params = &cur_mcs_inst->parent_group_obj_sub_params;
|
|
err = do_subscribe(conn, cur_mcs_inst->parent_group_obj_id_handle, sub_params);
|
|
if (err) {
|
|
BT_DBG("Could not subscribe: %d", err);
|
|
discovery_failure(conn, err);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
if (cur_mcs_inst->current_group_obj_id_handle &&
|
|
cur_mcs_inst->parent_group_obj_sub_params.disc_params == NULL) {
|
|
sub_params = &cur_mcs_inst->current_group_obj_sub_params;
|
|
err = do_subscribe(conn, cur_mcs_inst->current_group_obj_id_handle, sub_params);
|
|
if (err) {
|
|
BT_DBG("Could not subscribe: %d", err);
|
|
discovery_failure(conn, err);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
#endif /* CONFIG_BT_MCC_OTS */
|
|
|
|
if (cur_mcs_inst->playing_order_handle &&
|
|
cur_mcs_inst->playing_order_sub_params.disc_params == NULL) {
|
|
sub_params = &cur_mcs_inst->playing_order_sub_params;
|
|
err = do_subscribe(conn, cur_mcs_inst->playing_order_handle, sub_params);
|
|
if (err) {
|
|
BT_DBG("Could not subscribe: %d", err);
|
|
discovery_failure(conn, err);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
if (cur_mcs_inst->media_state_handle &&
|
|
cur_mcs_inst->media_state_sub_params.disc_params == NULL) {
|
|
sub_params = &cur_mcs_inst->media_state_sub_params;
|
|
err = do_subscribe(conn, cur_mcs_inst->media_state_handle, sub_params);
|
|
if (err) {
|
|
BT_DBG("Could not subscribe: %d", err);
|
|
discovery_failure(conn, err);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
if (cur_mcs_inst->cp_handle &&
|
|
cur_mcs_inst->cp_sub_params.disc_params == NULL) {
|
|
sub_params = &cur_mcs_inst->cp_sub_params;
|
|
err = do_subscribe(conn, cur_mcs_inst->cp_handle, sub_params);
|
|
if (err) {
|
|
BT_DBG("Could not subscribe: %d", err);
|
|
discovery_failure(conn, err);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
if (cur_mcs_inst->opcodes_supported_handle &&
|
|
cur_mcs_inst->opcodes_supported_sub_params.disc_params == NULL) {
|
|
sub_params = &cur_mcs_inst->opcodes_supported_sub_params;
|
|
err = do_subscribe(conn, cur_mcs_inst->opcodes_supported_handle, sub_params);
|
|
if (err) {
|
|
BT_DBG("Could not subscribe: %d", err);
|
|
discovery_failure(conn, err);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
#ifdef CONFIG_BT_MCC_OTS
|
|
if (cur_mcs_inst->scp_handle &&
|
|
cur_mcs_inst->scp_sub_params.disc_params == NULL) {
|
|
sub_params = &cur_mcs_inst->scp_sub_params;
|
|
err = do_subscribe(conn, cur_mcs_inst->scp_handle, sub_params);
|
|
if (err) {
|
|
BT_DBG("Could not subscribe: %d", err);
|
|
discovery_failure(conn, err);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
if (cur_mcs_inst->search_results_obj_id_handle &&
|
|
cur_mcs_inst->search_results_obj_sub_params.disc_params == NULL) {
|
|
sub_params = &cur_mcs_inst->search_results_obj_sub_params;
|
|
err = do_subscribe(conn, cur_mcs_inst->search_results_obj_id_handle, sub_params);
|
|
if (err) {
|
|
BT_DBG("Could not subscribe: %d", err);
|
|
discovery_failure(conn, err);
|
|
}
|
|
return false;
|
|
}
|
|
#endif /* CONFIG_BT_MCC_OTS */
|
|
|
|
/* If we have come here, there are no more characteristics to
|
|
* subscribe to, and we are done.
|
|
*/
|
|
return true;
|
|
}
|
|
|
|
/* This function is called when characteristics are found.
|
|
* The function will store handles to GMCS characteristics.
|
|
* After this, the function will start subscription to characteristics
|
|
*/
|
|
static uint8_t discover_mcs_char_func(struct bt_conn *conn,
|
|
const struct bt_gatt_attr *attr,
|
|
struct bt_gatt_discover_params *params)
|
|
{
|
|
struct bt_gatt_chrc *chrc;
|
|
bool subscription_done = true;
|
|
|
|
if (attr) {
|
|
/* Found an attribute */
|
|
BT_DBG("[ATTRIBUTE] handle 0x%04X", attr->handle);
|
|
|
|
if (params->type != BT_GATT_DISCOVER_CHARACTERISTIC) {
|
|
/* But it was not a characteristic - continue search */
|
|
return BT_GATT_ITER_CONTINUE;
|
|
}
|
|
|
|
/* We have found an attribute, and it is a characteristic */
|
|
/* Find out which attribute, and subscribe if we should */
|
|
chrc = (struct bt_gatt_chrc *)attr->user_data;
|
|
|
|
if (!bt_uuid_cmp(chrc->uuid, BT_UUID_MCS_PLAYER_NAME)) {
|
|
BT_DBG("Player name, UUID: %s", bt_uuid_str(chrc->uuid));
|
|
cur_mcs_inst->player_name_handle = chrc->value_handle;
|
|
/* Use discovery params pointer as subscription flag */
|
|
cur_mcs_inst->player_name_sub_params.disc_params = NULL;
|
|
#ifdef CONFIG_BT_MCC_OTS
|
|
} else if (!bt_uuid_cmp(chrc->uuid, BT_UUID_MCS_ICON_OBJ_ID)) {
|
|
BT_DBG("Icon Object, UUID: %s", bt_uuid_str(chrc->uuid));
|
|
cur_mcs_inst->icon_obj_id_handle = chrc->value_handle;
|
|
#endif /* CONFIG_BT_MCC_OTS */
|
|
} else if (!bt_uuid_cmp(chrc->uuid, BT_UUID_MCS_ICON_URL)) {
|
|
BT_DBG("Icon URL, UUID: %s", bt_uuid_str(chrc->uuid));
|
|
cur_mcs_inst->icon_url_handle = chrc->value_handle;
|
|
} else if (!bt_uuid_cmp(chrc->uuid, BT_UUID_MCS_TRACK_CHANGED)) {
|
|
BT_DBG("Track Changed, UUID: %s", bt_uuid_str(chrc->uuid));
|
|
cur_mcs_inst->track_changed_handle = chrc->value_handle;
|
|
cur_mcs_inst->track_changed_sub_params.disc_params = NULL;
|
|
} else if (!bt_uuid_cmp(chrc->uuid, BT_UUID_MCS_TRACK_TITLE)) {
|
|
BT_DBG("Track Title, UUID: %s", bt_uuid_str(chrc->uuid));
|
|
cur_mcs_inst->track_title_handle = chrc->value_handle;
|
|
cur_mcs_inst->track_title_sub_params.disc_params = NULL;
|
|
} else if (!bt_uuid_cmp(chrc->uuid, BT_UUID_MCS_TRACK_DURATION)) {
|
|
BT_DBG("Track Duration, UUID: %s", bt_uuid_str(chrc->uuid));
|
|
cur_mcs_inst->track_duration_handle = chrc->value_handle;
|
|
cur_mcs_inst->track_duration_sub_params.disc_params = NULL;
|
|
} else if (!bt_uuid_cmp(chrc->uuid, BT_UUID_MCS_TRACK_POSITION)) {
|
|
BT_DBG("Track Position, UUID: %s", bt_uuid_str(chrc->uuid));
|
|
cur_mcs_inst->track_position_handle = chrc->value_handle;
|
|
cur_mcs_inst->track_position_sub_params.disc_params = NULL;
|
|
} else if (!bt_uuid_cmp(chrc->uuid, BT_UUID_MCS_PLAYBACK_SPEED)) {
|
|
BT_DBG("Playback Speed, UUID: %s", bt_uuid_str(chrc->uuid));
|
|
cur_mcs_inst->playback_speed_handle = chrc->value_handle;
|
|
cur_mcs_inst->playback_speed_sub_params.disc_params = NULL;
|
|
} else if (!bt_uuid_cmp(chrc->uuid, BT_UUID_MCS_SEEKING_SPEED)) {
|
|
BT_DBG("Seeking Speed, UUID: %s", bt_uuid_str(chrc->uuid));
|
|
cur_mcs_inst->seeking_speed_handle = chrc->value_handle;
|
|
cur_mcs_inst->seeking_speed_sub_params.disc_params = NULL;
|
|
#ifdef CONFIG_BT_MCC_OTS
|
|
} else if (!bt_uuid_cmp(chrc->uuid, BT_UUID_MCS_TRACK_SEGMENTS_OBJ_ID)) {
|
|
BT_DBG("Track Segments Object, UUID: %s", bt_uuid_str(chrc->uuid));
|
|
cur_mcs_inst->segments_obj_id_handle = chrc->value_handle;
|
|
} else if (!bt_uuid_cmp(chrc->uuid, BT_UUID_MCS_CURRENT_TRACK_OBJ_ID)) {
|
|
BT_DBG("Current Track Object, UUID: %s", bt_uuid_str(chrc->uuid));
|
|
cur_mcs_inst->current_track_obj_id_handle = chrc->value_handle;
|
|
cur_mcs_inst->current_track_obj_sub_params.disc_params = NULL;
|
|
} else if (!bt_uuid_cmp(chrc->uuid, BT_UUID_MCS_NEXT_TRACK_OBJ_ID)) {
|
|
BT_DBG("Next Track Object, UUID: %s", bt_uuid_str(chrc->uuid));
|
|
cur_mcs_inst->next_track_obj_id_handle = chrc->value_handle;
|
|
cur_mcs_inst->next_track_obj_sub_params.disc_params = NULL;
|
|
} else if (!bt_uuid_cmp(chrc->uuid, BT_UUID_MCS_PARENT_GROUP_OBJ_ID)) {
|
|
BT_DBG("Parent Group Object, UUID: %s", bt_uuid_str(chrc->uuid));
|
|
cur_mcs_inst->parent_group_obj_id_handle = chrc->value_handle;
|
|
cur_mcs_inst->parent_group_obj_sub_params.disc_params = NULL;
|
|
} else if (!bt_uuid_cmp(chrc->uuid, BT_UUID_MCS_CURRENT_GROUP_OBJ_ID)) {
|
|
BT_DBG("Group Object, UUID: %s", bt_uuid_str(chrc->uuid));
|
|
cur_mcs_inst->current_group_obj_id_handle = chrc->value_handle;
|
|
cur_mcs_inst->current_group_obj_sub_params.disc_params = NULL;
|
|
#endif /* CONFIG_BT_MCC_OTS */
|
|
} else if (!bt_uuid_cmp(chrc->uuid, BT_UUID_MCS_PLAYING_ORDER)) {
|
|
BT_DBG("Playing Order, UUID: %s", bt_uuid_str(chrc->uuid));
|
|
cur_mcs_inst->playing_order_handle = chrc->value_handle;
|
|
cur_mcs_inst->playing_order_sub_params.disc_params = NULL;
|
|
} else if (!bt_uuid_cmp(chrc->uuid, BT_UUID_MCS_PLAYING_ORDERS)) {
|
|
BT_DBG("Playing Orders supported, UUID: %s", bt_uuid_str(chrc->uuid));
|
|
cur_mcs_inst->playing_orders_supported_handle = chrc->value_handle;
|
|
} else if (!bt_uuid_cmp(chrc->uuid, BT_UUID_MCS_MEDIA_STATE)) {
|
|
BT_DBG("Media State, UUID: %s", bt_uuid_str(chrc->uuid));
|
|
cur_mcs_inst->media_state_handle = chrc->value_handle;
|
|
cur_mcs_inst->media_state_sub_params.disc_params = NULL;
|
|
} else if (!bt_uuid_cmp(chrc->uuid, BT_UUID_MCS_MEDIA_CONTROL_POINT)) {
|
|
BT_DBG("Media Control Point, UUID: %s", bt_uuid_str(chrc->uuid));
|
|
cur_mcs_inst->cp_handle = chrc->value_handle;
|
|
cur_mcs_inst->cp_sub_params.disc_params = NULL;
|
|
} else if (!bt_uuid_cmp(chrc->uuid, BT_UUID_MCS_MEDIA_CONTROL_OPCODES)) {
|
|
BT_DBG("Media control opcodes supported, UUID: %s",
|
|
bt_uuid_str(chrc->uuid));
|
|
cur_mcs_inst->opcodes_supported_handle = chrc->value_handle;
|
|
cur_mcs_inst->opcodes_supported_sub_params.disc_params = NULL;
|
|
#ifdef CONFIG_BT_MCC_OTS
|
|
} else if (!bt_uuid_cmp(chrc->uuid, BT_UUID_MCS_SEARCH_CONTROL_POINT)) {
|
|
BT_DBG("Search control point, UUID: %s", bt_uuid_str(chrc->uuid));
|
|
cur_mcs_inst->scp_handle = chrc->value_handle;
|
|
cur_mcs_inst->scp_sub_params.disc_params = NULL;
|
|
} else if (!bt_uuid_cmp(chrc->uuid, BT_UUID_MCS_SEARCH_RESULTS_OBJ_ID)) {
|
|
BT_DBG("Search Results object, UUID: %s", bt_uuid_str(chrc->uuid));
|
|
cur_mcs_inst->search_results_obj_id_handle = chrc->value_handle;
|
|
cur_mcs_inst->search_results_obj_sub_params.disc_params = NULL;
|
|
#endif /* CONFIG_BT_MCC_OTS */
|
|
} else if (!bt_uuid_cmp(chrc->uuid, BT_UUID_CCID)) {
|
|
BT_DBG("Content Control ID, UUID: %s", bt_uuid_str(chrc->uuid));
|
|
cur_mcs_inst->content_control_id_handle = chrc->value_handle;
|
|
}
|
|
|
|
|
|
/* Continue to search for more attributes */
|
|
return BT_GATT_ITER_CONTINUE;
|
|
}
|
|
|
|
/* No more attributes found */
|
|
BT_DBG("GMCS characteristics found");
|
|
(void)memset(params, 0, sizeof(*params));
|
|
|
|
/* Either subscribe to characteristics, or continue to discovery of
|
|
*included services.
|
|
* Subscription is done after discovery, not in parallel with it,
|
|
* to avoid queuing many ATT requests that requires buffers.
|
|
*/
|
|
if (subscribe_all) {
|
|
subscription_done = subscribe_next_mcs_char(conn);
|
|
}
|
|
|
|
if (subscription_done) {
|
|
/* Not subscribing, or there was nothing to subscribe to */
|
|
#ifdef CONFIG_BT_MCC_OTS
|
|
/* Start discovery of included services to find OTS */
|
|
discover_included(conn);
|
|
#else
|
|
/* If OTS is not configured, discovery ends here */
|
|
if (mcc_cb && mcc_cb->discover_mcs) {
|
|
mcc_cb->discover_mcs(conn, 0);
|
|
}
|
|
#endif /* CONFIG_BT_MCC_OTS */
|
|
}
|
|
|
|
return BT_GATT_ITER_STOP;
|
|
}
|
|
|
|
/* This function is called when a (primary) GMCS service has been discovered.
|
|
* The function will store the start and end handle for the service. It will
|
|
* then start discovery of the characteristics of the GMCS service.
|
|
*/
|
|
static uint8_t discover_primary_func(struct bt_conn *conn,
|
|
const struct bt_gatt_attr *attr,
|
|
struct bt_gatt_discover_params *params)
|
|
{
|
|
struct bt_gatt_service_val *prim_service;
|
|
|
|
if (attr) {
|
|
int err;
|
|
/* Found an attribute */
|
|
BT_DBG("[ATTRIBUTE] handle 0x%04X", attr->handle);
|
|
|
|
if (params->type != BT_GATT_DISCOVER_PRIMARY) {
|
|
/* But it was not a primary service - continue search */
|
|
BT_WARN("Unexpected parameters");
|
|
return BT_GATT_ITER_CONTINUE;
|
|
}
|
|
|
|
/* We have found an attribute, and it is a primary service */
|
|
/* (Must be GMCS, since that is the one we searched for.) */
|
|
BT_DBG("Primary discovery complete");
|
|
BT_DBG("UUID: %s", bt_uuid_str(attr->uuid));
|
|
prim_service = (struct bt_gatt_service_val *)attr->user_data;
|
|
BT_DBG("UUID: %s", bt_uuid_str(prim_service->uuid));
|
|
|
|
cur_mcs_inst = &mcs_inst;
|
|
cur_mcs_inst->start_handle = attr->handle + 1;
|
|
cur_mcs_inst->end_handle = prim_service->end_handle;
|
|
|
|
/* Start discovery of characteristics */
|
|
discover_params.uuid = NULL;
|
|
discover_params.start_handle = cur_mcs_inst->start_handle;
|
|
discover_params.end_handle = cur_mcs_inst->end_handle;
|
|
discover_params.type = BT_GATT_DISCOVER_CHARACTERISTIC;
|
|
discover_params.func = discover_mcs_char_func;
|
|
|
|
BT_DBG("Start discovery of GMCS characteristics");
|
|
err = bt_gatt_discover(conn, &discover_params);
|
|
if (err) {
|
|
BT_DBG("Discovery failed: %d", err);
|
|
discovery_failure(conn, err);
|
|
}
|
|
return BT_GATT_ITER_STOP;
|
|
}
|
|
|
|
/* No attribute of the searched for type found */
|
|
BT_DBG("Could not find an GMCS instance on the server");
|
|
cur_mcs_inst = NULL;
|
|
if (mcc_cb && mcc_cb->discover_mcs) {
|
|
mcc_cb->discover_mcs(conn, -ENODATA);
|
|
}
|
|
return BT_GATT_ITER_STOP;
|
|
}
|
|
|
|
|
|
int bt_mcc_init(struct bt_mcc_cb *cb)
|
|
{
|
|
mcc_cb = cb;
|
|
|
|
#ifdef CONFIG_BT_MCC_OTS
|
|
/* Set up the callbacks from OTC */
|
|
/* TODO: Have one single content callback. */
|
|
/* For now: Use the icon callback for content - it is the first, */
|
|
/* and this will anyway be reset later. */
|
|
otc_cb.obj_data_read = on_icon_content;
|
|
otc_cb.obj_selected = on_obj_selected;
|
|
otc_cb.obj_metadata_read = on_object_metadata;
|
|
|
|
BT_DBG("Object selected callback: %p", otc_cb.obj_selected);
|
|
BT_DBG("Object content callback: %p", otc_cb.obj_data_read);
|
|
BT_DBG("Object metadata callback: %p", otc_cb.obj_metadata_read);
|
|
#endif /* CONFIG_BT_MCC_OTS */
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/* Initiate discovery.
|
|
* Discovery is handled by a chain of functions, where each function does its
|
|
* part, and then initiates a further discovery, with a new callback function.
|
|
*
|
|
* The order of discovery is follows:
|
|
* 1: Discover GMCS primary service (started here)
|
|
* 2: Discover characteristics of GMCS
|
|
* 3: Subscribe to characteristics of GMCS
|
|
* 4: Discover OTS service included in GMCS
|
|
* 5: Discover characteristics of OTS and subscribe to them
|
|
*/
|
|
int bt_mcc_discover_mcs(struct bt_conn *conn, bool subscribe)
|
|
{
|
|
CHECKIF(!conn) {
|
|
return -EINVAL;
|
|
} else if (cur_mcs_inst) {
|
|
return -EBUSY;
|
|
}
|
|
|
|
subscribe_all = subscribe;
|
|
memset(&discover_params, 0, sizeof(discover_params));
|
|
memset(&mcs_inst, 0, sizeof(mcs_inst));
|
|
(void)memcpy(&uuid, BT_UUID_GMCS, sizeof(uuid));
|
|
|
|
discover_params.func = discover_primary_func;
|
|
discover_params.uuid = &uuid.uuid;
|
|
discover_params.type = BT_GATT_DISCOVER_PRIMARY;
|
|
discover_params.start_handle = BT_ATT_FIRST_ATTRIBUTE_HANDLE;
|
|
discover_params.end_handle = BT_ATT_LAST_ATTRIBUTE_HANDLE;
|
|
|
|
BT_DBG("start discovery of GMCS primary service");
|
|
return bt_gatt_discover(conn, &discover_params);
|
|
}
|
|
|
|
|
|
int bt_mcc_read_player_name(struct bt_conn *conn)
|
|
{
|
|
int err;
|
|
|
|
CHECKIF(!conn) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!cur_mcs_inst->player_name_handle) {
|
|
BT_DBG("Handle not set");
|
|
return -EINVAL;
|
|
} else if (cur_mcs_inst->busy) {
|
|
return -EBUSY;
|
|
}
|
|
|
|
read_params.func = mcc_read_player_name_cb;
|
|
read_params.handle_count = 1;
|
|
read_params.single.handle = cur_mcs_inst->player_name_handle;
|
|
read_params.single.offset = 0U;
|
|
|
|
err = bt_gatt_read(conn, &read_params);
|
|
if (!err) {
|
|
cur_mcs_inst->busy = true;
|
|
}
|
|
return err;
|
|
}
|
|
|
|
|
|
#ifdef CONFIG_BT_MCC_OTS
|
|
int bt_mcc_read_icon_obj_id(struct bt_conn *conn)
|
|
{
|
|
int err;
|
|
|
|
CHECKIF(!conn) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!cur_mcs_inst->icon_obj_id_handle) {
|
|
BT_DBG("Handle not set");
|
|
return -EINVAL;
|
|
} else if (cur_mcs_inst->busy) {
|
|
return -EBUSY;
|
|
}
|
|
|
|
read_params.func = mcc_read_icon_obj_id_cb;
|
|
read_params.handle_count = 1;
|
|
read_params.single.handle = cur_mcs_inst->icon_obj_id_handle;
|
|
read_params.single.offset = 0U;
|
|
|
|
err = bt_gatt_read(conn, &read_params);
|
|
if (!err) {
|
|
cur_mcs_inst->busy = true;
|
|
}
|
|
return err;
|
|
}
|
|
#endif /* CONFIG_BT_MCC_OTS */
|
|
|
|
int bt_mcc_read_icon_url(struct bt_conn *conn)
|
|
{
|
|
int err;
|
|
|
|
CHECKIF(!conn) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!cur_mcs_inst->icon_url_handle) {
|
|
BT_DBG("Handle not set");
|
|
return -EINVAL;
|
|
} else if (cur_mcs_inst->busy) {
|
|
return -EBUSY;
|
|
}
|
|
|
|
read_params.func = mcc_read_icon_url_cb;
|
|
read_params.handle_count = 1;
|
|
read_params.single.handle = cur_mcs_inst->icon_url_handle;
|
|
read_params.single.offset = 0U;
|
|
|
|
err = bt_gatt_read(conn, &read_params);
|
|
if (!err) {
|
|
cur_mcs_inst->busy = true;
|
|
}
|
|
return err;
|
|
}
|
|
|
|
int bt_mcc_read_track_title(struct bt_conn *conn)
|
|
{
|
|
int err;
|
|
|
|
CHECKIF(!conn) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!cur_mcs_inst->track_title_handle) {
|
|
BT_DBG("Handle not set");
|
|
return -EINVAL;
|
|
} else if (cur_mcs_inst->busy) {
|
|
return -EBUSY;
|
|
}
|
|
|
|
read_params.func = mcc_read_track_title_cb;
|
|
read_params.handle_count = 1;
|
|
read_params.single.handle = cur_mcs_inst->track_title_handle;
|
|
read_params.single.offset = 0U;
|
|
|
|
err = bt_gatt_read(conn, &read_params);
|
|
if (!err) {
|
|
cur_mcs_inst->busy = true;
|
|
}
|
|
return err;
|
|
}
|
|
|
|
int bt_mcc_read_track_duration(struct bt_conn *conn)
|
|
{
|
|
int err;
|
|
|
|
CHECKIF(!conn) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!cur_mcs_inst->track_duration_handle) {
|
|
BT_DBG("Handle not set");
|
|
return -EINVAL;
|
|
} else if (cur_mcs_inst->busy) {
|
|
return -EBUSY;
|
|
}
|
|
|
|
read_params.func = mcc_read_track_duration_cb;
|
|
read_params.handle_count = 1;
|
|
read_params.single.handle = cur_mcs_inst->track_duration_handle;
|
|
read_params.single.offset = 0U;
|
|
|
|
err = bt_gatt_read(conn, &read_params);
|
|
if (!err) {
|
|
cur_mcs_inst->busy = true;
|
|
}
|
|
return err;
|
|
}
|
|
|
|
int bt_mcc_read_track_position(struct bt_conn *conn)
|
|
{
|
|
int err;
|
|
|
|
CHECKIF(!conn) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!cur_mcs_inst->track_position_handle) {
|
|
BT_DBG("Handle not set");
|
|
return -EINVAL;
|
|
} else if (cur_mcs_inst->busy) {
|
|
return -EBUSY;
|
|
}
|
|
|
|
read_params.func = mcc_read_track_position_cb;
|
|
read_params.handle_count = 1;
|
|
read_params.single.handle = cur_mcs_inst->track_position_handle;
|
|
read_params.single.offset = 0U;
|
|
|
|
err = bt_gatt_read(conn, &read_params);
|
|
if (!err) {
|
|
cur_mcs_inst->busy = true;
|
|
}
|
|
return err;
|
|
}
|
|
|
|
int bt_mcc_set_track_position(struct bt_conn *conn, int32_t pos)
|
|
{
|
|
int err;
|
|
|
|
CHECKIF(!conn) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!cur_mcs_inst->track_position_handle) {
|
|
BT_DBG("Handle not set");
|
|
return -EINVAL;
|
|
} else if (cur_mcs_inst->busy) {
|
|
return -EBUSY;
|
|
}
|
|
|
|
(void)memcpy(cur_mcs_inst->write_buf, &pos, sizeof(pos));
|
|
|
|
cur_mcs_inst->write_params.offset = 0;
|
|
cur_mcs_inst->write_params.data = cur_mcs_inst->write_buf;
|
|
cur_mcs_inst->write_params.length = sizeof(pos);
|
|
cur_mcs_inst->write_params.handle = cur_mcs_inst->track_position_handle;
|
|
cur_mcs_inst->write_params.func = mcs_write_track_position_cb;
|
|
|
|
BT_HEXDUMP_DBG(cur_mcs_inst->write_params.data, sizeof(pos),
|
|
"Track position sent");
|
|
|
|
err = bt_gatt_write(conn, &cur_mcs_inst->write_params);
|
|
if (!err) {
|
|
cur_mcs_inst->busy = true;
|
|
}
|
|
return err;
|
|
}
|
|
|
|
int bt_mcc_read_playback_speed(struct bt_conn *conn)
|
|
{
|
|
int err;
|
|
|
|
CHECKIF(!conn) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!cur_mcs_inst->playback_speed_handle) {
|
|
BT_DBG("Handle not set");
|
|
return -EINVAL;
|
|
} else if (cur_mcs_inst->busy) {
|
|
return -EBUSY;
|
|
}
|
|
|
|
read_params.func = mcc_read_playback_speed_cb;
|
|
read_params.handle_count = 1;
|
|
read_params.single.handle = cur_mcs_inst->playback_speed_handle;
|
|
read_params.single.offset = 0U;
|
|
|
|
err = bt_gatt_read(conn, &read_params);
|
|
if (!err) {
|
|
cur_mcs_inst->busy = true;
|
|
}
|
|
return err;
|
|
}
|
|
|
|
int bt_mcc_set_playback_speed(struct bt_conn *conn, int8_t speed)
|
|
{
|
|
int err;
|
|
|
|
CHECKIF(!conn) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!cur_mcs_inst->playback_speed_handle) {
|
|
BT_DBG("Handle not set");
|
|
return -EINVAL;
|
|
} else if (cur_mcs_inst->busy) {
|
|
return -EBUSY;
|
|
}
|
|
|
|
(void)memcpy(cur_mcs_inst->write_buf, &speed, sizeof(speed));
|
|
|
|
cur_mcs_inst->write_params.offset = 0;
|
|
cur_mcs_inst->write_params.data = cur_mcs_inst->write_buf;
|
|
cur_mcs_inst->write_params.length = sizeof(speed);
|
|
cur_mcs_inst->write_params.handle = cur_mcs_inst->playback_speed_handle;
|
|
cur_mcs_inst->write_params.func = mcs_write_playback_speed_cb;
|
|
|
|
BT_HEXDUMP_DBG(cur_mcs_inst->write_params.data, sizeof(speed),
|
|
"Playback speed");
|
|
|
|
err = bt_gatt_write(conn, &cur_mcs_inst->write_params);
|
|
if (!err) {
|
|
cur_mcs_inst->busy = true;
|
|
}
|
|
return err;
|
|
}
|
|
|
|
int bt_mcc_read_seeking_speed(struct bt_conn *conn)
|
|
{
|
|
int err;
|
|
|
|
CHECKIF(!conn) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!cur_mcs_inst->seeking_speed_handle) {
|
|
BT_DBG("Handle not set");
|
|
return -EINVAL;
|
|
} else if (cur_mcs_inst->busy) {
|
|
return -EBUSY;
|
|
}
|
|
|
|
read_params.func = mcc_read_seeking_speed_cb;
|
|
read_params.handle_count = 1;
|
|
read_params.single.handle = cur_mcs_inst->seeking_speed_handle;
|
|
read_params.single.offset = 0U;
|
|
|
|
err = bt_gatt_read(conn, &read_params);
|
|
if (!err) {
|
|
cur_mcs_inst->busy = true;
|
|
}
|
|
return err;
|
|
}
|
|
|
|
#ifdef CONFIG_BT_MCC_OTS
|
|
int bt_mcc_read_segments_obj_id(struct bt_conn *conn)
|
|
{
|
|
int err;
|
|
|
|
CHECKIF(!conn) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!cur_mcs_inst->segments_obj_id_handle) {
|
|
BT_DBG("Handle not set");
|
|
return -EINVAL;
|
|
} else if (cur_mcs_inst->busy) {
|
|
return -EBUSY;
|
|
}
|
|
|
|
read_params.func = mcc_read_segments_obj_id_cb;
|
|
read_params.handle_count = 1;
|
|
read_params.single.handle = cur_mcs_inst->segments_obj_id_handle;
|
|
read_params.single.offset = 0U;
|
|
|
|
err = bt_gatt_read(conn, &read_params);
|
|
if (!err) {
|
|
cur_mcs_inst->busy = true;
|
|
}
|
|
return err;
|
|
}
|
|
|
|
int bt_mcc_read_current_track_obj_id(struct bt_conn *conn)
|
|
{
|
|
int err;
|
|
|
|
CHECKIF(!conn) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!cur_mcs_inst->current_track_obj_id_handle) {
|
|
BT_DBG("Handle not set");
|
|
return -EINVAL;
|
|
} else if (cur_mcs_inst->busy) {
|
|
return -EBUSY;
|
|
}
|
|
|
|
read_params.func = mcc_read_current_track_obj_id_cb;
|
|
read_params.handle_count = 1;
|
|
read_params.single.handle = cur_mcs_inst->current_track_obj_id_handle;
|
|
read_params.single.offset = 0U;
|
|
|
|
err = bt_gatt_read(conn, &read_params);
|
|
if (!err) {
|
|
cur_mcs_inst->busy = true;
|
|
}
|
|
return err;
|
|
}
|
|
|
|
int bt_mcc_set_current_track_obj_id(struct bt_conn *conn, uint64_t obj_id)
|
|
{
|
|
int err;
|
|
|
|
CHECKIF(!conn) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
CHECKIF(obj_id < BT_OTS_OBJ_ID_MIN || obj_id > BT_OTS_OBJ_ID_MAX) {
|
|
BT_DBG("Object ID invalid");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!cur_mcs_inst->current_track_obj_id_handle) {
|
|
BT_DBG("Handle not set");
|
|
return -EINVAL;
|
|
} else if (cur_mcs_inst->busy) {
|
|
return -EBUSY;
|
|
}
|
|
|
|
sys_put_le48(obj_id, cur_mcs_inst->write_buf);
|
|
cur_mcs_inst->write_params.offset = 0;
|
|
cur_mcs_inst->write_params.data = cur_mcs_inst->write_buf;
|
|
cur_mcs_inst->write_params.length = BT_OTS_OBJ_ID_SIZE;
|
|
cur_mcs_inst->write_params.handle = cur_mcs_inst->current_track_obj_id_handle;
|
|
cur_mcs_inst->write_params.func = mcs_write_current_track_obj_id_cb;
|
|
|
|
BT_HEXDUMP_DBG(cur_mcs_inst->write_params.data, BT_OTS_OBJ_ID_SIZE, "Object Id");
|
|
|
|
err = bt_gatt_write(conn, &cur_mcs_inst->write_params);
|
|
if (!err) {
|
|
cur_mcs_inst->busy = true;
|
|
}
|
|
return err;
|
|
}
|
|
|
|
int bt_mcc_read_next_track_obj_id(struct bt_conn *conn)
|
|
{
|
|
int err;
|
|
|
|
CHECKIF(!conn) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!cur_mcs_inst->next_track_obj_id_handle) {
|
|
BT_DBG("Handle not set");
|
|
return -EINVAL;
|
|
} else if (cur_mcs_inst->busy) {
|
|
return -EBUSY;
|
|
}
|
|
|
|
read_params.func = mcc_read_next_track_obj_id_cb;
|
|
read_params.handle_count = 1;
|
|
read_params.single.handle = cur_mcs_inst->next_track_obj_id_handle;
|
|
read_params.single.offset = 0U;
|
|
|
|
err = bt_gatt_read(conn, &read_params);
|
|
if (!err) {
|
|
cur_mcs_inst->busy = true;
|
|
}
|
|
return err;
|
|
}
|
|
|
|
int bt_mcc_set_next_track_obj_id(struct bt_conn *conn, uint64_t obj_id)
|
|
{
|
|
int err;
|
|
|
|
CHECKIF(!conn) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
CHECKIF(obj_id < BT_OTS_OBJ_ID_MIN || obj_id > BT_OTS_OBJ_ID_MAX) {
|
|
BT_DBG("Object ID invalid");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!cur_mcs_inst->next_track_obj_id_handle) {
|
|
BT_DBG("Handle not set");
|
|
return -EINVAL;
|
|
} else if (cur_mcs_inst->busy) {
|
|
return -EBUSY;
|
|
}
|
|
|
|
sys_put_le48(obj_id, cur_mcs_inst->write_buf);
|
|
cur_mcs_inst->write_params.offset = 0;
|
|
cur_mcs_inst->write_params.data = cur_mcs_inst->write_buf;
|
|
cur_mcs_inst->write_params.length = BT_OTS_OBJ_ID_SIZE;
|
|
cur_mcs_inst->write_params.handle = cur_mcs_inst->next_track_obj_id_handle;
|
|
cur_mcs_inst->write_params.func = mcs_write_next_track_obj_id_cb;
|
|
|
|
BT_HEXDUMP_DBG(cur_mcs_inst->write_params.data, BT_OTS_OBJ_ID_SIZE, "Object Id");
|
|
|
|
err = bt_gatt_write(conn, &cur_mcs_inst->write_params);
|
|
if (!err) {
|
|
cur_mcs_inst->busy = true;
|
|
}
|
|
return err;
|
|
}
|
|
|
|
int bt_mcc_read_parent_group_obj_id(struct bt_conn *conn)
|
|
{
|
|
int err;
|
|
|
|
CHECKIF(!conn) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!cur_mcs_inst->parent_group_obj_id_handle) {
|
|
BT_DBG("Handle not set");
|
|
return -EINVAL;
|
|
} else if (cur_mcs_inst->busy) {
|
|
return -EBUSY;
|
|
}
|
|
|
|
read_params.func = mcc_read_parent_group_obj_id_cb;
|
|
read_params.handle_count = 1;
|
|
read_params.single.handle = cur_mcs_inst->parent_group_obj_id_handle;
|
|
read_params.single.offset = 0U;
|
|
|
|
err = bt_gatt_read(conn, &read_params);
|
|
if (!err) {
|
|
cur_mcs_inst->busy = true;
|
|
}
|
|
return err;
|
|
}
|
|
|
|
int bt_mcc_read_current_group_obj_id(struct bt_conn *conn)
|
|
{
|
|
int err;
|
|
|
|
CHECKIF(!conn) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!cur_mcs_inst->current_group_obj_id_handle) {
|
|
BT_DBG("Handle not set");
|
|
return -EINVAL;
|
|
} else if (cur_mcs_inst->busy) {
|
|
return -EBUSY;
|
|
}
|
|
|
|
read_params.func = mcc_read_current_group_obj_id_cb;
|
|
read_params.handle_count = 1;
|
|
read_params.single.handle = cur_mcs_inst->current_group_obj_id_handle;
|
|
read_params.single.offset = 0U;
|
|
|
|
err = bt_gatt_read(conn, &read_params);
|
|
if (!err) {
|
|
cur_mcs_inst->busy = true;
|
|
}
|
|
return err;
|
|
}
|
|
|
|
int bt_mcc_set_current_group_obj_id(struct bt_conn *conn, uint64_t obj_id)
|
|
{
|
|
int err;
|
|
|
|
CHECKIF(!conn) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
CHECKIF(obj_id < BT_OTS_OBJ_ID_MIN || obj_id > BT_OTS_OBJ_ID_MAX) {
|
|
BT_DBG("Object ID invalid");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!cur_mcs_inst->current_group_obj_id_handle) {
|
|
BT_DBG("Handle not set");
|
|
return -EINVAL;
|
|
} else if (cur_mcs_inst->busy) {
|
|
return -EBUSY;
|
|
}
|
|
|
|
sys_put_le48(obj_id, cur_mcs_inst->write_buf);
|
|
cur_mcs_inst->write_params.offset = 0;
|
|
cur_mcs_inst->write_params.data = cur_mcs_inst->write_buf;
|
|
cur_mcs_inst->write_params.length = BT_OTS_OBJ_ID_SIZE;
|
|
cur_mcs_inst->write_params.handle = cur_mcs_inst->current_group_obj_id_handle;
|
|
cur_mcs_inst->write_params.func = mcs_write_current_group_obj_id_cb;
|
|
|
|
BT_HEXDUMP_DBG(cur_mcs_inst->write_params.data, BT_OTS_OBJ_ID_SIZE, "Object Id");
|
|
|
|
err = bt_gatt_write(conn, &cur_mcs_inst->write_params);
|
|
if (!err) {
|
|
cur_mcs_inst->busy = true;
|
|
}
|
|
return err;
|
|
}
|
|
#endif /* CONFIG_BT_MCC_OTS */
|
|
|
|
int bt_mcc_read_playing_order(struct bt_conn *conn)
|
|
{
|
|
int err;
|
|
|
|
CHECKIF(!conn) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!cur_mcs_inst->playing_order_handle) {
|
|
BT_DBG("Handle not set");
|
|
return -EINVAL;
|
|
} else if (cur_mcs_inst->busy) {
|
|
return -EBUSY;
|
|
}
|
|
|
|
read_params.func = mcc_read_playing_order_cb;
|
|
read_params.handle_count = 1;
|
|
read_params.single.handle = cur_mcs_inst->playing_order_handle;
|
|
read_params.single.offset = 0U;
|
|
|
|
err = bt_gatt_read(conn, &read_params);
|
|
if (!err) {
|
|
cur_mcs_inst->busy = true;
|
|
}
|
|
return err;
|
|
}
|
|
|
|
int bt_mcc_set_playing_order(struct bt_conn *conn, uint8_t order)
|
|
{
|
|
int err;
|
|
|
|
CHECKIF(!conn) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!cur_mcs_inst->playing_order_handle) {
|
|
BT_DBG("Handle not set");
|
|
return -EINVAL;
|
|
} else if (cur_mcs_inst->busy) {
|
|
return -EBUSY;
|
|
}
|
|
|
|
(void)memcpy(cur_mcs_inst->write_buf, &order, sizeof(order));
|
|
|
|
cur_mcs_inst->write_params.offset = 0;
|
|
cur_mcs_inst->write_params.data = cur_mcs_inst->write_buf;
|
|
cur_mcs_inst->write_params.length = sizeof(order);
|
|
cur_mcs_inst->write_params.handle = cur_mcs_inst->playing_order_handle;
|
|
cur_mcs_inst->write_params.func = mcs_write_playing_order_cb;
|
|
|
|
BT_HEXDUMP_DBG(cur_mcs_inst->write_params.data, sizeof(order),
|
|
"Playing order");
|
|
|
|
err = bt_gatt_write(conn, &cur_mcs_inst->write_params);
|
|
if (!err) {
|
|
cur_mcs_inst->busy = true;
|
|
}
|
|
return err;
|
|
}
|
|
|
|
int bt_mcc_read_playing_orders_supported(struct bt_conn *conn)
|
|
{
|
|
int err;
|
|
|
|
CHECKIF(!conn) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!cur_mcs_inst->playing_orders_supported_handle) {
|
|
BT_DBG("Handle not set");
|
|
return -EINVAL;
|
|
} else if (cur_mcs_inst->busy) {
|
|
return -EBUSY;
|
|
}
|
|
|
|
read_params.func = mcc_read_playing_orders_supported_cb;
|
|
read_params.handle_count = 1;
|
|
read_params.single.handle = cur_mcs_inst->playing_orders_supported_handle;
|
|
read_params.single.offset = 0U;
|
|
|
|
err = bt_gatt_read(conn, &read_params);
|
|
if (!err) {
|
|
cur_mcs_inst->busy = true;
|
|
}
|
|
return err;
|
|
}
|
|
|
|
int bt_mcc_read_media_state(struct bt_conn *conn)
|
|
{
|
|
int err;
|
|
|
|
CHECKIF(!conn) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!cur_mcs_inst->media_state_handle) {
|
|
BT_DBG("Handle not set");
|
|
return -EINVAL;
|
|
} else if (cur_mcs_inst->busy) {
|
|
return -EBUSY;
|
|
}
|
|
|
|
read_params.func = mcc_read_media_state_cb;
|
|
read_params.handle_count = 1;
|
|
read_params.single.handle = cur_mcs_inst->media_state_handle;
|
|
read_params.single.offset = 0U;
|
|
|
|
err = bt_gatt_read(conn, &read_params);
|
|
if (!err) {
|
|
cur_mcs_inst->busy = true;
|
|
}
|
|
return err;
|
|
}
|
|
|
|
int bt_mcc_send_cmd(struct bt_conn *conn, const struct mpl_cmd *cmd)
|
|
{
|
|
int err;
|
|
int length = sizeof(cmd->opcode);
|
|
|
|
CHECKIF(!conn) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!cur_mcs_inst->cp_handle) {
|
|
BT_DBG("Handle not set");
|
|
return -EINVAL;
|
|
} else if (cur_mcs_inst->busy) {
|
|
return -EBUSY;
|
|
}
|
|
|
|
(void)memcpy(cur_mcs_inst->write_buf, &cmd->opcode, length);
|
|
if (cmd->use_param) {
|
|
length += sizeof(cmd->param);
|
|
(void)memcpy(&cur_mcs_inst->write_buf[sizeof(cmd->opcode)], &cmd->param,
|
|
sizeof(cmd->param));
|
|
}
|
|
|
|
cur_mcs_inst->write_params.offset = 0;
|
|
cur_mcs_inst->write_params.data = cur_mcs_inst->write_buf;
|
|
cur_mcs_inst->write_params.length = length;
|
|
cur_mcs_inst->write_params.handle = cur_mcs_inst->cp_handle;
|
|
cur_mcs_inst->write_params.func = mcs_write_cp_cb;
|
|
|
|
BT_HEXDUMP_DBG(cur_mcs_inst->write_params.data, sizeof(*cmd),
|
|
"Command sent");
|
|
|
|
err = bt_gatt_write(conn, &cur_mcs_inst->write_params);
|
|
if (!err) {
|
|
cur_mcs_inst->busy = true;
|
|
}
|
|
return err;
|
|
}
|
|
|
|
int bt_mcc_read_opcodes_supported(struct bt_conn *conn)
|
|
{
|
|
int err;
|
|
|
|
CHECKIF(!conn) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!cur_mcs_inst->opcodes_supported_handle) {
|
|
BT_DBG("Handle not set");
|
|
return -EINVAL;
|
|
} else if (cur_mcs_inst->busy) {
|
|
return -EBUSY;
|
|
}
|
|
|
|
read_params.func = mcc_read_opcodes_supported_cb;
|
|
read_params.handle_count = 1;
|
|
read_params.single.handle = cur_mcs_inst->opcodes_supported_handle;
|
|
read_params.single.offset = 0U;
|
|
|
|
err = bt_gatt_read(conn, &read_params);
|
|
if (!err) {
|
|
cur_mcs_inst->busy = true;
|
|
}
|
|
return err;
|
|
}
|
|
|
|
#ifdef CONFIG_BT_MCC_OTS
|
|
int bt_mcc_send_search(struct bt_conn *conn, const struct mpl_search *search)
|
|
{
|
|
int err;
|
|
|
|
CHECKIF(!conn) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!cur_mcs_inst->scp_handle) {
|
|
BT_DBG("Handle not set");
|
|
return -EINVAL;
|
|
} else if (cur_mcs_inst->busy) {
|
|
return -EBUSY;
|
|
}
|
|
|
|
(void)memcpy(cur_mcs_inst->write_buf, &search->search, search->len);
|
|
|
|
cur_mcs_inst->write_params.offset = 0;
|
|
cur_mcs_inst->write_params.data = cur_mcs_inst->write_buf;
|
|
cur_mcs_inst->write_params.length = search->len;
|
|
cur_mcs_inst->write_params.handle = cur_mcs_inst->scp_handle;
|
|
cur_mcs_inst->write_params.func = mcs_write_scp_cb;
|
|
|
|
BT_HEXDUMP_DBG(cur_mcs_inst->write_params.data, search->len,
|
|
"Search sent");
|
|
|
|
err = bt_gatt_write(conn, &cur_mcs_inst->write_params);
|
|
if (!err) {
|
|
cur_mcs_inst->busy = true;
|
|
}
|
|
return err;
|
|
}
|
|
|
|
int bt_mcc_read_search_results_obj_id(struct bt_conn *conn)
|
|
{
|
|
int err;
|
|
|
|
CHECKIF(!conn) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!cur_mcs_inst->search_results_obj_id_handle) {
|
|
BT_DBG("Handle not set");
|
|
return -EINVAL;
|
|
} else if (cur_mcs_inst->busy) {
|
|
return -EBUSY;
|
|
}
|
|
|
|
read_params.func = mcc_read_search_results_obj_id_cb;
|
|
read_params.handle_count = 1;
|
|
read_params.single.handle = cur_mcs_inst->search_results_obj_id_handle;
|
|
read_params.single.offset = 0U;
|
|
|
|
err = bt_gatt_read(conn, &read_params);
|
|
if (!err) {
|
|
cur_mcs_inst->busy = true;
|
|
}
|
|
return err;
|
|
}
|
|
#endif /* CONFIG_BT_MCC_OTS */
|
|
|
|
int bt_mcc_read_content_control_id(struct bt_conn *conn)
|
|
{
|
|
int err;
|
|
|
|
CHECKIF(!conn) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!cur_mcs_inst->content_control_id_handle) {
|
|
BT_DBG("Handle not set");
|
|
return -EINVAL;
|
|
} else if (cur_mcs_inst->busy) {
|
|
return -EBUSY;
|
|
}
|
|
|
|
read_params.func = mcc_read_content_control_id_cb;
|
|
read_params.handle_count = 1;
|
|
read_params.single.handle = cur_mcs_inst->content_control_id_handle;
|
|
read_params.single.offset = 0U;
|
|
|
|
err = bt_gatt_read(conn, &read_params);
|
|
if (!err) {
|
|
cur_mcs_inst->busy = true;
|
|
}
|
|
return err;
|
|
}
|
|
|
|
#ifdef CONFIG_BT_MCC_OTS
|
|
|
|
void on_obj_selected(struct bt_ots_client *otc_inst,
|
|
struct bt_conn *conn, int result)
|
|
{
|
|
BT_DBG("Current object selected");
|
|
/* TODO: Read metadata here? */
|
|
/* For now: Left to the application */
|
|
|
|
/* Only one object at a time is selected in OTS */
|
|
/* When the selected callback comes, a new object is selected */
|
|
/* Reset the object buffer */
|
|
net_buf_simple_reset(&otc_obj_buf);
|
|
|
|
if (mcc_cb && mcc_cb->otc_obj_selected) {
|
|
mcc_cb->otc_obj_selected(conn, OLCP_RESULT_TO_ERROR(result));
|
|
}
|
|
}
|
|
|
|
/* TODO: Merge the object callback functions into one */
|
|
/* Use a notion of the "active" object, as done in mpl.c, for tracking */
|
|
int on_icon_content(struct bt_ots_client *otc_inst, struct bt_conn *conn,
|
|
uint32_t offset, uint32_t len, uint8_t *data_p,
|
|
bool is_complete)
|
|
{
|
|
int cb_err = 0;
|
|
|
|
BT_DBG("Received Media Player Icon content, %i bytes at offset %i",
|
|
len, offset);
|
|
|
|
BT_HEXDUMP_DBG(data_p, len, "Icon content");
|
|
|
|
if (len > net_buf_simple_tailroom(&otc_obj_buf)) {
|
|
BT_WARN("Can not fit whole object");
|
|
cb_err = -EMSGSIZE;
|
|
}
|
|
|
|
net_buf_simple_add_mem(&otc_obj_buf, data_p,
|
|
MIN(net_buf_simple_tailroom(&otc_obj_buf), len));
|
|
|
|
if (is_complete) {
|
|
BT_DBG("Icon object received");
|
|
|
|
if (mcc_cb && mcc_cb->otc_icon_object) {
|
|
mcc_cb->otc_icon_object(conn, cb_err, &otc_obj_buf);
|
|
}
|
|
/* Reset buf in case the same object is read again without */
|
|
/* calling select in between */
|
|
net_buf_simple_reset(&otc_obj_buf);
|
|
}
|
|
|
|
return BT_OTS_CONTINUE;
|
|
}
|
|
|
|
#if CONFIG_BT_DEBUG_MCC
|
|
struct track_seg_t {
|
|
uint8_t name_len;
|
|
char name[CONFIG_BT_MCC_SEGMENT_NAME_MAX];
|
|
int32_t pos;
|
|
};
|
|
|
|
struct track_segs_t {
|
|
uint16_t cnt;
|
|
struct track_seg_t segs[CONFIG_BT_MCC_TRACK_SEGS_MAX_CNT];
|
|
};
|
|
|
|
static void decode_track_segments(struct net_buf_simple *buff,
|
|
struct track_segs_t *track_segs)
|
|
{
|
|
uint16_t i;
|
|
struct track_seg_t *seg;
|
|
uint8_t *name;
|
|
struct net_buf_simple tmp_buf;
|
|
|
|
/* Copy the buf, to not consume the original in this debug function */
|
|
net_buf_simple_clone(buff, &tmp_buf);
|
|
|
|
while (tmp_buf.len &&
|
|
track_segs->cnt < CONFIG_BT_MCC_TRACK_SEGS_MAX_CNT) {
|
|
|
|
i = track_segs->cnt++;
|
|
seg = &track_segs->segs[i];
|
|
|
|
seg->name_len = net_buf_simple_pull_u8(&tmp_buf);
|
|
if (seg->name_len + sizeof(int32_t) > tmp_buf.len) {
|
|
BT_WARN("Segment too long");
|
|
return;
|
|
}
|
|
|
|
if (seg->name_len) {
|
|
|
|
name = net_buf_simple_pull_mem(&tmp_buf, seg->name_len);
|
|
|
|
if (seg->name_len >= CONFIG_BT_MCC_SEGMENT_NAME_MAX) {
|
|
seg->name_len =
|
|
CONFIG_BT_MCC_SEGMENT_NAME_MAX - 1;
|
|
}
|
|
(void)memcpy(seg->name, name, seg->name_len);
|
|
}
|
|
seg->name[seg->name_len] = '\0';
|
|
|
|
track_segs->segs[i].pos = (int32_t)net_buf_simple_pull_le32(&tmp_buf);
|
|
}
|
|
}
|
|
#endif /* CONFIG_BT_DEBUG_MCC */
|
|
|
|
int on_track_segments_content(struct bt_ots_client *otc_inst,
|
|
struct bt_conn *conn, uint32_t offset,
|
|
uint32_t len, uint8_t *data_p, bool is_complete)
|
|
{
|
|
int cb_err = 0;
|
|
|
|
BT_DBG("Received Track Segments content, %i bytes at offset %i",
|
|
len, offset);
|
|
|
|
if (len > net_buf_simple_tailroom(&otc_obj_buf)) {
|
|
BT_WARN("Can not fit whole object");
|
|
cb_err = -EMSGSIZE;
|
|
}
|
|
|
|
net_buf_simple_add_mem(&otc_obj_buf, data_p,
|
|
MIN(net_buf_simple_tailroom(&otc_obj_buf), len));
|
|
|
|
if (is_complete) {
|
|
BT_DBG("Track segment object received");
|
|
|
|
#if CONFIG_BT_DEBUG_MCC
|
|
struct track_segs_t track_segments;
|
|
|
|
track_segments.cnt = 0;
|
|
decode_track_segments(&otc_obj_buf, &track_segments);
|
|
for (int i = 0; i < track_segments.cnt; i++) {
|
|
BT_DBG("Track segment %i:", i);
|
|
BT_DBG("\t-Name\t:%s",
|
|
track_segments.segs[i].name);
|
|
BT_DBG("\t-Position\t:%d", track_segments.segs[i].pos);
|
|
}
|
|
#endif /* CONFIG_BT_DEBUG_MCC */
|
|
|
|
if (mcc_cb && mcc_cb->otc_track_segments_object) {
|
|
mcc_cb->otc_track_segments_object(conn,
|
|
cb_err, &otc_obj_buf);
|
|
}
|
|
|
|
net_buf_simple_reset(&otc_obj_buf);
|
|
}
|
|
|
|
return BT_OTS_CONTINUE;
|
|
}
|
|
|
|
int on_current_track_content(struct bt_ots_client *otc_inst,
|
|
struct bt_conn *conn, uint32_t offset,
|
|
uint32_t len, uint8_t *data_p, bool is_complete)
|
|
{
|
|
int cb_err = 0;
|
|
|
|
BT_DBG("Received Current Track content, %i bytes at offset %i",
|
|
len, offset);
|
|
|
|
BT_HEXDUMP_DBG(data_p, len, "Track content");
|
|
|
|
if (len > net_buf_simple_tailroom(&otc_obj_buf)) {
|
|
BT_WARN("Can not fit whole object");
|
|
cb_err = -EMSGSIZE;
|
|
}
|
|
|
|
net_buf_simple_add_mem(&otc_obj_buf, data_p,
|
|
MIN(net_buf_simple_tailroom(&otc_obj_buf), len));
|
|
|
|
if (is_complete) {
|
|
BT_DBG("Current Track Object received");
|
|
|
|
if (mcc_cb && mcc_cb->otc_current_track_object) {
|
|
mcc_cb->otc_current_track_object(conn, cb_err, &otc_obj_buf);
|
|
}
|
|
|
|
net_buf_simple_reset(&otc_obj_buf);
|
|
}
|
|
|
|
return BT_OTS_CONTINUE;
|
|
}
|
|
|
|
int on_next_track_content(struct bt_ots_client *otc_inst,
|
|
struct bt_conn *conn, uint32_t offset, uint32_t len,
|
|
uint8_t *data_p, bool is_complete)
|
|
{
|
|
int cb_err = 0;
|
|
|
|
BT_DBG("Received Next Track content, %i bytes at offset %i",
|
|
len, offset);
|
|
|
|
BT_HEXDUMP_DBG(data_p, len, "Track content");
|
|
|
|
if (len > net_buf_simple_tailroom(&otc_obj_buf)) {
|
|
BT_WARN("Can not fit whole object");
|
|
cb_err = -EMSGSIZE;
|
|
}
|
|
|
|
net_buf_simple_add_mem(&otc_obj_buf, data_p,
|
|
MIN(net_buf_simple_tailroom(&otc_obj_buf), len));
|
|
|
|
if (is_complete) {
|
|
BT_DBG("Next Track Object received");
|
|
|
|
if (mcc_cb && mcc_cb->otc_next_track_object) {
|
|
mcc_cb->otc_next_track_object(conn, cb_err, &otc_obj_buf);
|
|
}
|
|
|
|
net_buf_simple_reset(&otc_obj_buf);
|
|
}
|
|
|
|
return BT_OTS_CONTINUE;
|
|
}
|
|
|
|
|
|
#if CONFIG_BT_DEBUG_MCC
|
|
struct id_list_elem_t {
|
|
uint8_t type;
|
|
uint64_t id;
|
|
};
|
|
|
|
struct id_list_t {
|
|
struct id_list_elem_t ids[CONFIG_BT_MCC_GROUP_RECORDS_MAX];
|
|
uint16_t cnt;
|
|
};
|
|
|
|
static void decode_group(struct net_buf_simple *buff,
|
|
struct id_list_t *ids)
|
|
{
|
|
struct net_buf_simple tmp_buf;
|
|
|
|
/* Copy the buf, to not consume the original in this debug function */
|
|
net_buf_simple_clone(buff, &tmp_buf);
|
|
|
|
while ((tmp_buf.len) && (ids->cnt < CONFIG_BT_MCC_GROUP_RECORDS_MAX)) {
|
|
ids->ids[ids->cnt].type = net_buf_simple_pull_u8(&tmp_buf);
|
|
ids->ids[ids->cnt++].id = net_buf_simple_pull_le48(&tmp_buf);
|
|
}
|
|
}
|
|
#endif /* CONFIG_BT_DEBUG_MCC */
|
|
|
|
int on_parent_group_content(struct bt_ots_client *otc_inst,
|
|
struct bt_conn *conn, uint32_t offset,
|
|
uint32_t len, uint8_t *data_p, bool is_complete)
|
|
{
|
|
int cb_err = 0;
|
|
|
|
BT_DBG("Received Parent Group content, %i bytes at offset %i",
|
|
len, offset);
|
|
|
|
BT_HEXDUMP_DBG(data_p, len, "Group content");
|
|
|
|
if (len > net_buf_simple_tailroom(&otc_obj_buf)) {
|
|
BT_WARN("Can not fit whole object");
|
|
cb_err = -EMSGSIZE;
|
|
}
|
|
|
|
net_buf_simple_add_mem(&otc_obj_buf, data_p,
|
|
MIN(net_buf_simple_tailroom(&otc_obj_buf), len));
|
|
|
|
if (is_complete) {
|
|
BT_DBG("Parent Group object received");
|
|
|
|
#if CONFIG_BT_DEBUG_MCC
|
|
struct id_list_t group = {0};
|
|
|
|
decode_group(&otc_obj_buf, &group);
|
|
for (int i = 0; i < group.cnt; i++) {
|
|
char t[BT_OTS_OBJ_ID_STR_LEN];
|
|
|
|
(void)bt_ots_obj_id_to_str(group.ids[i].id, t,
|
|
BT_OTS_OBJ_ID_STR_LEN);
|
|
BT_DBG("Object type: %d, object ID: %s",
|
|
group.ids[i].type, t);
|
|
}
|
|
#endif /* CONFIG_BT_DEBUG_MCC */
|
|
|
|
if (mcc_cb && mcc_cb->otc_parent_group_object) {
|
|
mcc_cb->otc_parent_group_object(conn, cb_err, &otc_obj_buf);
|
|
}
|
|
|
|
net_buf_simple_reset(&otc_obj_buf);
|
|
}
|
|
|
|
return BT_OTS_CONTINUE;
|
|
}
|
|
|
|
int on_current_group_content(struct bt_ots_client *otc_inst,
|
|
struct bt_conn *conn, uint32_t offset,
|
|
uint32_t len, uint8_t *data_p, bool is_complete)
|
|
{
|
|
int cb_err = 0;
|
|
|
|
BT_DBG("Received Current Group content, %i bytes at offset %i",
|
|
len, offset);
|
|
|
|
BT_HEXDUMP_DBG(data_p, len, "Group content");
|
|
|
|
if (len > net_buf_simple_tailroom(&otc_obj_buf)) {
|
|
BT_WARN("Can not fit whole object");
|
|
cb_err = -EMSGSIZE;
|
|
}
|
|
|
|
net_buf_simple_add_mem(&otc_obj_buf, data_p,
|
|
MIN(net_buf_simple_tailroom(&otc_obj_buf), len));
|
|
|
|
if (is_complete) {
|
|
BT_DBG("Current Group object received");
|
|
|
|
#if CONFIG_BT_DEBUG_MCC
|
|
struct id_list_t group = {0};
|
|
|
|
decode_group(&otc_obj_buf, &group);
|
|
for (int i = 0; i < group.cnt; i++) {
|
|
char t[BT_OTS_OBJ_ID_STR_LEN];
|
|
|
|
(void)bt_ots_obj_id_to_str(group.ids[i].id, t,
|
|
BT_OTS_OBJ_ID_STR_LEN);
|
|
BT_DBG("Object type: %d, object ID: %s",
|
|
group.ids[i].type, t);
|
|
}
|
|
#endif /* CONFIG_BT_DEBUG_MCC */
|
|
|
|
if (mcc_cb && mcc_cb->otc_current_group_object) {
|
|
mcc_cb->otc_current_group_object(conn, cb_err, &otc_obj_buf);
|
|
}
|
|
|
|
net_buf_simple_reset(&otc_obj_buf);
|
|
}
|
|
|
|
return BT_OTS_CONTINUE;
|
|
}
|
|
|
|
void on_object_metadata(struct bt_ots_client *otc_inst,
|
|
struct bt_conn *conn, int err,
|
|
uint8_t metadata_read)
|
|
{
|
|
BT_INFO("Object's meta data:");
|
|
BT_INFO("\tCurrent size\t:%u", otc_inst->cur_object.size.cur);
|
|
|
|
if (otc_inst->cur_object.size.cur > otc_obj_buf.size) {
|
|
BT_DBG("Object larger than allocated buffer");
|
|
}
|
|
|
|
bt_ots_metadata_display(&otc_inst->cur_object, 1);
|
|
|
|
if (mcc_cb && mcc_cb->otc_obj_metadata) {
|
|
mcc_cb->otc_obj_metadata(conn, err);
|
|
}
|
|
}
|
|
|
|
int bt_mcc_otc_read_object_metadata(struct bt_conn *conn)
|
|
{
|
|
int err;
|
|
|
|
err = bt_ots_client_read_object_metadata(&cur_mcs_inst->otc, conn,
|
|
BT_OTS_METADATA_REQ_ALL);
|
|
if (err) {
|
|
BT_DBG("Error reading the object: %d", err);
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
|
|
int bt_mcc_otc_read_icon_object(struct bt_conn *conn)
|
|
{
|
|
int err;
|
|
/* TODO: Add handling for busy - either MCS or OTS */
|
|
|
|
cur_mcs_inst->otc.cb->obj_data_read = on_icon_content;
|
|
|
|
err = bt_ots_client_read_object_data(&cur_mcs_inst->otc, conn);
|
|
if (err) {
|
|
BT_DBG("Error reading the object: %d", err);
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
int bt_mcc_otc_read_track_segments_object(struct bt_conn *conn)
|
|
{
|
|
int err;
|
|
|
|
/* TODO: Add handling for busy - either MCS or OTS */
|
|
|
|
/* TODO: Assumes object is already selected */
|
|
cur_mcs_inst->otc.cb->obj_data_read = on_track_segments_content;
|
|
|
|
err = bt_ots_client_read_object_data(&cur_mcs_inst->otc, conn);
|
|
if (err) {
|
|
BT_DBG("Error reading the object: %d", err);
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
int bt_mcc_otc_read_current_track_object(struct bt_conn *conn)
|
|
{
|
|
int err;
|
|
|
|
/* TODO: Add handling for busy - either MCS or OTS */
|
|
|
|
/* TODO: Assumes object is already selected */
|
|
cur_mcs_inst->otc.cb->obj_data_read = on_current_track_content;
|
|
|
|
err = bt_ots_client_read_object_data(&cur_mcs_inst->otc, conn);
|
|
if (err) {
|
|
BT_DBG("Error reading the object: %d", err);
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
int bt_mcc_otc_read_next_track_object(struct bt_conn *conn)
|
|
{
|
|
int err;
|
|
|
|
/* TODO: Add handling for busy - either MCS or OTS */
|
|
|
|
/* TODO: Assumes object is already selected */
|
|
cur_mcs_inst->otc.cb->obj_data_read = on_next_track_content;
|
|
|
|
err = bt_ots_client_read_object_data(&cur_mcs_inst->otc, conn);
|
|
if (err) {
|
|
BT_DBG("Error reading the object: %d", err);
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
int bt_mcc_otc_read_parent_group_object(struct bt_conn *conn)
|
|
{
|
|
int err;
|
|
|
|
/* TODO: Add handling for busy - either MCS or OTS */
|
|
|
|
/* TODO: Assumes object is already selected */
|
|
|
|
/* Reuse callback for current group */
|
|
cur_mcs_inst->otc.cb->obj_data_read = on_parent_group_content;
|
|
|
|
err = bt_ots_client_read_object_data(&cur_mcs_inst->otc, conn);
|
|
if (err) {
|
|
BT_DBG("Error reading the object: %d", err);
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
int bt_mcc_otc_read_current_group_object(struct bt_conn *conn)
|
|
{
|
|
int err;
|
|
|
|
/* TODO: Add handling for busy - either MCS or OTS */
|
|
|
|
/* TODO: Assumes object is already selected */
|
|
cur_mcs_inst->otc.cb->obj_data_read = on_current_group_content;
|
|
|
|
err = bt_ots_client_read_object_data(&cur_mcs_inst->otc, conn);
|
|
if (err) {
|
|
BT_DBG("Error reading the object: %d", err);
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
#if defined(CONFIG_BT_MCC_SHELL)
|
|
struct bt_ots_client *bt_mcc_otc_inst(void)
|
|
{
|
|
return &cur_mcs_inst->otc;
|
|
}
|
|
#endif /* defined(CONFIG_BT_MCC_SHELL) */
|
|
|
|
#endif /* CONFIG_BT_MCC_OTS */
|