/** @file * @brief Bluetooth Media Control Service */ /* * Copyright (c) 2019 - 2021 Nordic Semiconductor ASA * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "audio_internal.h" #include "media_proxy_internal.h" #include "mcs_internal.h" LOG_MODULE_REGISTER(bt_mcs, CONFIG_BT_MCS_LOG_LEVEL); static void notify(const struct bt_uuid *uuid, const void *data, uint16_t len); static struct media_proxy_sctrl_cbs cbs; enum { FLAG_PLAYER_NAME_CHANGED, FLAG_ICON_URL_CHANGED, FLAG_TRACK_CHANGED, FLAG_TRACK_TITLE_CHANGED, FLAG_TRACK_DURATION_CHANGED, FLAG_TRACK_POSITION_CHANGED, FLAG_PLAYBACK_SPEED_CHANGED, FLAG_SEEKING_SPEED_CHANGED, FLAG_PLAYING_ORDER_CHANGED, FLAG_MEDIA_STATE_CHANGED, FLAG_MEDIA_CONTROL_OPCODES_CHANGED, FLAG_MEDIA_CONTROL_POINT_BUSY, FLAG_MEDIA_CONTROL_POINT_RESULT, #if defined(CONFIG_BT_OTS) FLAG_CURRENT_TRACK_OBJ_ID_CHANGED, FLAG_NEXT_TRACK_OBJ_ID_CHANGED, FLAG_PARENT_GROUP_OBJ_ID_CHANGED, FLAG_CURRENT_GROUP_OBJ_ID_CHANGED, FLAG_SEARCH_RESULTS_OBJ_ID_CHANGED, FLAG_SEARCH_CONTROL_POINT_BUSY, FLAG_SEARCH_CONTROL_POINT_RESULT, #endif /* CONFIG_BT_OTS */ FLAG_NUM, }; static struct client_state { ATOMIC_DEFINE(flags, FLAG_NUM); struct mpl_cmd_ntf cmd_ntf; #if defined(CONFIG_BT_OTS) uint8_t search_control_point_result; #endif /* CONFIG_BT_OTS */ } clients[CONFIG_BT_MAX_CONN]; static void disconnected(struct bt_conn *conn, uint8_t reason) { /* Clear data on disconnect */ memset(&clients[bt_conn_index(conn)], 0, sizeof(struct client_state)); } BT_CONN_CB_DEFINE(conn_callbacks) = { .disconnected = disconnected, }; /* Functions for reading and writing attributes, and for keeping track * of attribute configuration changes. * Functions for notifications are placed after the service definition. */ static ssize_t read_player_name(struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, uint16_t len, uint16_t offset) { const char *name = media_proxy_sctrl_get_player_name(); LOG_DBG("Player name read: %s (offset %u)", name, offset); if (conn != NULL) { struct client_state *client = &clients[bt_conn_index(conn)]; if (offset == 0) { atomic_clear_bit(client->flags, FLAG_PLAYER_NAME_CHANGED); } else if (atomic_test_bit(client->flags, FLAG_PLAYER_NAME_CHANGED)) { return BT_GATT_ERR(BT_MCS_ERR_LONG_VAL_CHANGED); } } return bt_gatt_attr_read(conn, attr, buf, len, offset, name, strlen(name)); } static void player_name_cfg_changed(const struct bt_gatt_attr *attr, uint16_t value) { LOG_DBG("value 0x%04x", value); } #ifdef CONFIG_BT_OTS static ssize_t read_icon_id(struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, uint16_t len, uint16_t offset) { uint64_t icon_id = media_proxy_sctrl_get_icon_id(); uint8_t icon_id_le[BT_OTS_OBJ_ID_SIZE]; sys_put_le48(icon_id, icon_id_le); LOG_DBG_OBJ_ID("Icon object read: ", icon_id); return bt_gatt_attr_read(conn, attr, buf, len, offset, icon_id_le, sizeof(icon_id_le)); } #endif /* CONFIG_BT_OTS */ static ssize_t read_icon_url(struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, uint16_t len, uint16_t offset) { const char *url = media_proxy_sctrl_get_icon_url(); LOG_DBG("Icon URL read, offset: %d, len:%d, URL: %s", offset, len, url); if (conn != NULL) { struct client_state *client = &clients[bt_conn_index(conn)]; if (offset == 0) { atomic_clear_bit(client->flags, FLAG_ICON_URL_CHANGED); } else if (atomic_test_bit(client->flags, FLAG_ICON_URL_CHANGED)) { return BT_GATT_ERR(BT_MCS_ERR_LONG_VAL_CHANGED); } } return bt_gatt_attr_read(conn, attr, buf, len, offset, url, strlen(url)); } static void track_cfg_changed(const struct bt_gatt_attr *attr, uint16_t value) { LOG_DBG("value 0x%04x", value); } static ssize_t read_track_title(struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, uint16_t len, uint16_t offset) { const char *title = media_proxy_sctrl_get_track_title(); LOG_DBG("Track title read, offset: %d, len:%d, title: %s", offset, len, title); if (conn != NULL) { struct client_state *client = &clients[bt_conn_index(conn)]; if (offset == 0) { atomic_clear_bit(client->flags, FLAG_TRACK_TITLE_CHANGED); } else if (atomic_test_bit(client->flags, FLAG_TRACK_TITLE_CHANGED)) { return BT_GATT_ERR(BT_MCS_ERR_LONG_VAL_CHANGED); } } return bt_gatt_attr_read(conn, attr, buf, len, offset, title, strlen(title)); } static void track_title_cfg_changed(const struct bt_gatt_attr *attr, uint16_t value) { LOG_DBG("value 0x%04x", value); } static ssize_t read_track_duration(struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, uint16_t len, uint16_t offset) { int32_t duration = media_proxy_sctrl_get_track_duration(); int32_t duration_le = sys_cpu_to_le32(duration); LOG_DBG("Track duration read: %d (0x%08x)", duration, duration); if (conn != NULL) { struct client_state *client = &clients[bt_conn_index(conn)]; atomic_clear_bit(client->flags, FLAG_TRACK_DURATION_CHANGED); } return bt_gatt_attr_read(conn, attr, buf, len, offset, &duration_le, sizeof(duration_le)); } static void track_duration_cfg_changed(const struct bt_gatt_attr *attr, uint16_t value) { LOG_DBG("value 0x%04x", value); } static ssize_t read_track_position(struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, uint16_t len, uint16_t offset) { int32_t position = media_proxy_sctrl_get_track_position(); int32_t position_le = sys_cpu_to_le32(position); LOG_DBG("Track position read: %d (0x%08x)", position, position); if (conn != NULL) { struct client_state *client = &clients[bt_conn_index(conn)]; atomic_clear_bit(client->flags, FLAG_TRACK_POSITION_CHANGED); } return bt_gatt_attr_read(conn, attr, buf, len, offset, &position_le, sizeof(position_le)); } static ssize_t write_track_position(struct bt_conn *conn, const struct bt_gatt_attr *attr, const void *buf, uint16_t len, uint16_t offset, uint8_t flags) { int32_t position; if (offset != 0) { return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET); } if (len != sizeof(position)) { return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN); } position = sys_get_le32((uint8_t *)buf); media_proxy_sctrl_set_track_position(position); LOG_DBG("Track position write: %d", position); return len; } static void track_position_cfg_changed(const struct bt_gatt_attr *attr, uint16_t value) { LOG_DBG("value 0x%04x", value); } static ssize_t read_playback_speed(struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, uint16_t len, uint16_t offset) { int8_t speed = media_proxy_sctrl_get_playback_speed(); LOG_DBG("Playback speed read: %d", speed); if (conn != NULL) { struct client_state *client = &clients[bt_conn_index(conn)]; atomic_clear_bit(client->flags, FLAG_PLAYBACK_SPEED_CHANGED); } return bt_gatt_attr_read(conn, attr, buf, len, offset, &speed, sizeof(speed)); } static ssize_t write_playback_speed(struct bt_conn *conn, const struct bt_gatt_attr *attr, const void *buf, uint16_t len, uint16_t offset, uint8_t flags) { int8_t speed; if (offset != 0) { return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET); } if (len != sizeof(speed)) { return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN); } memcpy(&speed, buf, len); media_proxy_sctrl_set_playback_speed(speed); LOG_DBG("Playback speed write: %d", speed); return len; } static void playback_speed_cfg_changed(const struct bt_gatt_attr *attr, uint16_t value) { LOG_DBG("value 0x%04x", value); } static ssize_t read_seeking_speed(struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, uint16_t len, uint16_t offset) { int8_t speed = media_proxy_sctrl_get_seeking_speed(); LOG_DBG("Seeking speed read: %d", speed); if (conn != NULL) { struct client_state *client = &clients[bt_conn_index(conn)]; atomic_clear_bit(client->flags, FLAG_SEEKING_SPEED_CHANGED); } return bt_gatt_attr_read(conn, attr, buf, len, offset, &speed, sizeof(speed)); } static void seeking_speed_cfg_changed(const struct bt_gatt_attr *attr, uint16_t value) { LOG_DBG("value 0x%04x", value); } #ifdef CONFIG_BT_OTS static ssize_t read_track_segments_id(struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, uint16_t len, uint16_t offset) { uint64_t track_segments_id = media_proxy_sctrl_get_track_segments_id(); uint8_t track_segments_id_le[BT_OTS_OBJ_ID_SIZE]; sys_put_le48(track_segments_id, track_segments_id_le); LOG_DBG_OBJ_ID("Track segments ID read: ", track_segments_id); return bt_gatt_attr_read(conn, attr, buf, len, offset, track_segments_id_le, sizeof(track_segments_id_le)); } static ssize_t read_current_track_id(struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, uint16_t len, uint16_t offset) { uint64_t track_id = media_proxy_sctrl_get_current_track_id(); uint8_t track_id_le[BT_OTS_OBJ_ID_SIZE]; sys_put_le48(track_id, track_id_le); LOG_DBG_OBJ_ID("Current track ID read: ", track_id); if (conn != NULL) { struct client_state *client = &clients[bt_conn_index(conn)]; atomic_clear_bit(client->flags, FLAG_CURRENT_TRACK_OBJ_ID_CHANGED); } return bt_gatt_attr_read(conn, attr, buf, len, offset, track_id_le, sizeof(track_id_le)); } static ssize_t write_current_track_id(struct bt_conn *conn, const struct bt_gatt_attr *attr, const void *buf, uint16_t len, uint16_t offset, uint8_t flags) { uint64_t id; if (offset != 0) { LOG_DBG("Invalid offset"); return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET); } if (len != BT_OTS_OBJ_ID_SIZE) { LOG_DBG("Invalid length"); return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN); } id = sys_get_le48((uint8_t *)buf); if (IS_ENABLED(CONFIG_BT_MCS_LOG_LEVEL_DBG)) { char str[BT_OTS_OBJ_ID_STR_LEN]; (void)bt_ots_obj_id_to_str(id, str, sizeof(str)); LOG_DBG("Current track write: offset: %d, len: %d, track ID: %s", offset, len, str); } media_proxy_sctrl_set_current_track_id(id); return BT_OTS_OBJ_ID_SIZE; } static void current_track_id_cfg_changed(const struct bt_gatt_attr *attr, uint16_t value) { LOG_DBG("value 0x%04x", value); } static ssize_t read_next_track_id(struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, uint16_t len, uint16_t offset) { uint64_t track_id = media_proxy_sctrl_get_next_track_id(); uint8_t track_id_le[BT_OTS_OBJ_ID_SIZE]; sys_put_le48(track_id, track_id_le); if (conn != NULL) { struct client_state *client = &clients[bt_conn_index(conn)]; atomic_clear_bit(client->flags, FLAG_NEXT_TRACK_OBJ_ID_CHANGED); } if (track_id == MPL_NO_TRACK_ID) { LOG_DBG("Next track read, but it is empty"); /* "If the media player has no next track, the length of the */ /* characteristic shall be zero." */ return bt_gatt_attr_read(conn, attr, buf, len, offset, NULL, 0); } LOG_DBG_OBJ_ID("Next track read: ", track_id); return bt_gatt_attr_read(conn, attr, buf, len, offset, track_id_le, sizeof(track_id_le)); } static ssize_t write_next_track_id(struct bt_conn *conn, const struct bt_gatt_attr *attr, const void *buf, uint16_t len, uint16_t offset, uint8_t flags) { uint64_t id; if (offset != 0) { LOG_DBG("Invalid offset"); return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET); } if (len != BT_OTS_OBJ_ID_SIZE) { LOG_DBG("Invalid length"); return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN); } id = sys_get_le48((uint8_t *)buf); if (IS_ENABLED(CONFIG_BT_MCS_LOG_LEVEL_DBG)) { char str[BT_OTS_OBJ_ID_STR_LEN]; (void)bt_ots_obj_id_to_str(id, str, sizeof(str)); LOG_DBG("Next track write: offset: %d, len: %d, track ID: %s", offset, len, str); } media_proxy_sctrl_set_next_track_id(id); return BT_OTS_OBJ_ID_SIZE; } static void next_track_id_cfg_changed(const struct bt_gatt_attr *attr, uint16_t value) { LOG_DBG("value 0x%04x", value); } static ssize_t read_parent_group_id(struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, uint16_t len, uint16_t offset) { uint64_t group_id = media_proxy_sctrl_get_parent_group_id(); uint8_t group_id_le[BT_OTS_OBJ_ID_SIZE]; sys_put_le48(group_id, group_id_le); LOG_DBG_OBJ_ID("Parent group read: ", group_id); if (conn != NULL) { struct client_state *client = &clients[bt_conn_index(conn)]; atomic_clear_bit(client->flags, FLAG_PARENT_GROUP_OBJ_ID_CHANGED); } return bt_gatt_attr_read(conn, attr, buf, len, offset, group_id_le, sizeof(group_id_le)); } static void parent_group_id_cfg_changed(const struct bt_gatt_attr *attr, uint16_t value) { LOG_DBG("value 0x%04x", value); } static ssize_t read_current_group_id(struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, uint16_t len, uint16_t offset) { uint64_t group_id = media_proxy_sctrl_get_current_group_id(); uint8_t group_id_le[BT_OTS_OBJ_ID_SIZE]; sys_put_le48(group_id, group_id_le); LOG_DBG_OBJ_ID("Current group read: ", group_id); if (conn != NULL) { struct client_state *client = &clients[bt_conn_index(conn)]; atomic_clear_bit(client->flags, FLAG_CURRENT_GROUP_OBJ_ID_CHANGED); } return bt_gatt_attr_read(conn, attr, buf, len, offset, group_id_le, sizeof(group_id_le)); } static ssize_t write_current_group_id(struct bt_conn *conn, const struct bt_gatt_attr *attr, const void *buf, uint16_t len, uint16_t offset, uint8_t flags) { uint64_t id; if (offset != 0) { LOG_DBG("Invalid offset"); return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET); } if (len != BT_OTS_OBJ_ID_SIZE) { LOG_DBG("Invalid length"); return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN); } id = sys_get_le48((uint8_t *)buf); if (IS_ENABLED(CONFIG_BT_MCS_LOG_LEVEL_DBG)) { char str[BT_OTS_OBJ_ID_STR_LEN]; (void)bt_ots_obj_id_to_str(id, str, sizeof(str)); LOG_DBG("Current group ID write: offset: %d, len: %d, track ID: %s", offset, len, str); } media_proxy_sctrl_set_current_group_id(id); return BT_OTS_OBJ_ID_SIZE; } static void current_group_id_cfg_changed(const struct bt_gatt_attr *attr, uint16_t value) { LOG_DBG("value 0x%04x", value); } #endif /* CONFIG_BT_OTS */ static ssize_t read_playing_order(struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, uint16_t len, uint16_t offset) { uint8_t order = media_proxy_sctrl_get_playing_order(); LOG_DBG("Playing order read: %d (0x%02x)", order, order); if (conn != NULL) { struct client_state *client = &clients[bt_conn_index(conn)]; atomic_clear_bit(client->flags, FLAG_PLAYING_ORDER_CHANGED); } return bt_gatt_attr_read(conn, attr, buf, len, offset, &order, sizeof(order)); } static ssize_t write_playing_order(struct bt_conn *conn, const struct bt_gatt_attr *attr, const void *buf, uint16_t len, uint16_t offset, uint8_t flags) { LOG_DBG("Playing order write"); int8_t order; if (offset != 0) { return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET); } if (len != sizeof(order)) { return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN); } memcpy(&order, buf, len); media_proxy_sctrl_set_playing_order(order); LOG_DBG("Playing order write: %d", order); return len; } static void playing_order_cfg_changed(const struct bt_gatt_attr *attr, uint16_t value) { LOG_DBG("value 0x%04x", value); } static ssize_t read_playing_orders_supported(struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, uint16_t len, uint16_t offset) { uint16_t orders = media_proxy_sctrl_get_playing_orders_supported(); uint16_t orders_le = sys_cpu_to_le16(orders); LOG_DBG("Playing orders read: %d (0x%04x)", orders, orders); return bt_gatt_attr_read(conn, attr, buf, len, offset, &orders_le, sizeof(orders_le)); } static ssize_t read_media_state(struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, uint16_t len, uint16_t offset) { uint8_t state = media_proxy_sctrl_get_media_state(); LOG_DBG("Media state read: %d", state); if (conn != NULL) { struct client_state *client = &clients[bt_conn_index(conn)]; atomic_clear_bit(client->flags, FLAG_MEDIA_STATE_CHANGED); } return bt_gatt_attr_read(conn, attr, buf, len, offset, &state, sizeof(state)); } static void media_state_cfg_changed(const struct bt_gatt_attr *attr, uint16_t value) { LOG_DBG("value 0x%04x", value); } static ssize_t write_control_point(struct bt_conn *conn, const struct bt_gatt_attr *attr, const void *buf, uint16_t len, uint16_t offset, uint8_t flags) { struct mpl_cmd command; if (offset != 0) { return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET); } if (len != sizeof(command.opcode) && len != sizeof(command.opcode) + sizeof(command.param)) { return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN); } memcpy(&command.opcode, buf, sizeof(command.opcode)); LOG_DBG("Opcode: %d", command.opcode); command.use_param = false; if (!BT_MCS_VALID_OP(command.opcode)) { /* MCS does not specify what to return in case of an error - Only what to notify*/ const struct mpl_cmd_ntf cmd_ntf = { .requested_opcode = command.opcode, .result_code = BT_MCS_OPC_NTF_NOT_SUPPORTED, }; LOG_DBG("Opcode 0x%02X is invalid", command.opcode); notify(BT_UUID_MCS_MEDIA_CONTROL_POINT, &cmd_ntf, sizeof(cmd_ntf)); return BT_GATT_ERR(BT_ATT_ERR_VALUE_NOT_ALLOWED); } if (conn != NULL) { struct client_state *client = &clients[bt_conn_index(conn)]; if (atomic_test_and_set_bit(client->flags, FLAG_MEDIA_CONTROL_POINT_BUSY)) { const struct mpl_cmd_ntf cmd_ntf = { .requested_opcode = command.opcode, .result_code = BT_MCS_OPC_NTF_CANNOT_BE_COMPLETED, }; LOG_DBG("Busy with other operation"); notify(BT_UUID_MCS_MEDIA_CONTROL_POINT, &cmd_ntf, sizeof(cmd_ntf)); return BT_GATT_ERR(BT_ATT_ERR_PROCEDURE_IN_PROGRESS); } } if (len == sizeof(command.opcode) + sizeof(command.param)) { command.param = sys_get_le32((char *)buf + sizeof(command.opcode)); command.use_param = true; LOG_DBG("Parameter: %d", command.param); } media_proxy_sctrl_send_command(&command); return len; } static void control_point_cfg_changed(const struct bt_gatt_attr *attr, uint16_t value) { LOG_DBG("value 0x%04x", value); } static ssize_t read_opcodes_supported(struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, uint16_t len, uint16_t offset) { uint32_t opcodes = media_proxy_sctrl_get_commands_supported(); uint32_t opcodes_le = sys_cpu_to_le32(opcodes); LOG_DBG("Opcodes_supported read: %d (0x%08x)", opcodes, opcodes); if (conn != NULL) { struct client_state *client = &clients[bt_conn_index(conn)]; atomic_clear_bit(client->flags, FLAG_MEDIA_CONTROL_OPCODES_CHANGED); } return bt_gatt_attr_read(conn, attr, buf, len, offset, &opcodes_le, sizeof(opcodes_le)); } static void opcodes_supported_cfg_changed(const struct bt_gatt_attr *attr, uint16_t value) { LOG_DBG("value 0x%04x", value); } #ifdef CONFIG_BT_OTS static ssize_t write_search_control_point(struct bt_conn *conn, const struct bt_gatt_attr *attr, const void *buf, uint16_t len, uint16_t offset, uint8_t flags) { struct mpl_search search = {0}; if (offset != 0) { return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET); } if (len > SEARCH_LEN_MAX || len < SEARCH_LEN_MIN) { return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN); } if (conn != NULL) { struct client_state *client = &clients[bt_conn_index(conn)]; if (atomic_test_and_set_bit(client->flags, FLAG_SEARCH_CONTROL_POINT_BUSY)) { const uint8_t result_code = BT_MCS_SCP_NTF_FAILURE; LOG_DBG("Busy with other operation"); notify(BT_UUID_MCS_SEARCH_CONTROL_POINT, &result_code, sizeof(result_code)); return BT_GATT_ERR(BT_ATT_ERR_PROCEDURE_IN_PROGRESS); } } memcpy(&search.search, (char *)buf, len); search.len = len; LOG_DBG("Search length: %d", len); LOG_HEXDUMP_DBG(&search.search, search.len, "Search content"); media_proxy_sctrl_send_search(&search); return len; } static void search_control_point_cfg_changed(const struct bt_gatt_attr *attr, uint16_t value) { LOG_DBG("value 0x%04x", value); } static ssize_t read_search_results_id(struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, uint16_t len, uint16_t offset) { uint64_t search_id = media_proxy_sctrl_get_search_results_id(); LOG_DBG_OBJ_ID("Search results id read: ", search_id); if (conn != NULL) { struct client_state *client = &clients[bt_conn_index(conn)]; atomic_clear_bit(client->flags, FLAG_SEARCH_RESULTS_OBJ_ID_CHANGED); } /* TODO: The permanent solution here should be that the call to */ /* mpl should fill the UUID in a pointed-to value, and return a */ /* length or an error code, to indicate whether this ID has a */ /* value now. This should be done for all functions of this kind. */ /* For now, fix the issue here - send zero-length data if the */ /* ID is zero. */ /* *Spec requirement - IDs may not be valid, in which case the */ /* characteristic shall be zero length. */ if (search_id == 0) { return bt_gatt_attr_read(conn, attr, buf, len, offset, NULL, 0); } else { uint8_t search_id_le[BT_OTS_OBJ_ID_SIZE]; sys_put_le48(search_id, search_id_le); return bt_gatt_attr_read(conn, attr, buf, len, offset, &search_id_le, sizeof(search_id_le)); } } static void search_results_id_cfg_changed(const struct bt_gatt_attr *attr, uint16_t value) { LOG_DBG("value 0x%04x", value); } #endif /* CONFIG_BT_OTS */ static ssize_t read_content_ctrl_id(struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, uint16_t len, uint16_t offset) { uint8_t id = media_proxy_sctrl_get_content_ctrl_id(); LOG_DBG("Content control ID read: %d", id); return bt_gatt_attr_read(conn, attr, buf, len, offset, &id, sizeof(id)); } /* Defines for OTS-dependent characteristics - empty if no OTS */ #ifdef CONFIG_BT_OTS #define ICON_OBJ_ID_CHARACTERISTIC_IF_OTS \ BT_AUDIO_CHRC(BT_UUID_MCS_ICON_OBJ_ID, \ BT_GATT_CHRC_READ, \ BT_GATT_PERM_READ_ENCRYPT, \ read_icon_id, NULL, NULL), #define SEGMENTS_TRACK_GROUP_ID_CHARACTERISTICS_IF_OTS \ BT_AUDIO_CHRC(BT_UUID_MCS_TRACK_SEGMENTS_OBJ_ID, \ BT_GATT_CHRC_READ, \ BT_GATT_PERM_READ_ENCRYPT, \ read_track_segments_id, NULL, NULL), \ BT_AUDIO_CHRC(BT_UUID_MCS_CURRENT_TRACK_OBJ_ID, \ BT_GATT_CHRC_READ | BT_GATT_CHRC_WRITE | \ BT_GATT_CHRC_WRITE_WITHOUT_RESP | BT_GATT_CHRC_NOTIFY, \ BT_GATT_PERM_READ_ENCRYPT | BT_GATT_PERM_WRITE_ENCRYPT, \ read_current_track_id, write_current_track_id, NULL), \ BT_AUDIO_CCC(current_track_id_cfg_changed), \ BT_AUDIO_CHRC(BT_UUID_MCS_NEXT_TRACK_OBJ_ID, \ BT_GATT_CHRC_READ | BT_GATT_CHRC_WRITE | \ BT_GATT_CHRC_WRITE_WITHOUT_RESP | BT_GATT_CHRC_NOTIFY, \ BT_GATT_PERM_READ_ENCRYPT | BT_GATT_PERM_WRITE_ENCRYPT, \ read_next_track_id, write_next_track_id, NULL), \ BT_AUDIO_CCC(next_track_id_cfg_changed), \ BT_AUDIO_CHRC(BT_UUID_MCS_PARENT_GROUP_OBJ_ID, \ BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY, \ BT_GATT_PERM_READ_ENCRYPT, \ read_parent_group_id, NULL, NULL), \ BT_AUDIO_CCC(parent_group_id_cfg_changed), \ BT_AUDIO_CHRC(BT_UUID_MCS_CURRENT_GROUP_OBJ_ID, \ BT_GATT_CHRC_READ | BT_GATT_CHRC_WRITE | \ BT_GATT_CHRC_WRITE_WITHOUT_RESP | BT_GATT_CHRC_NOTIFY, \ BT_GATT_PERM_READ_ENCRYPT | BT_GATT_PERM_WRITE_ENCRYPT, \ read_current_group_id, write_current_group_id, NULL), \ BT_AUDIO_CCC(current_group_id_cfg_changed), #define SEARCH_CHARACTERISTICS_IF_OTS \ BT_AUDIO_CHRC(BT_UUID_MCS_SEARCH_CONTROL_POINT, \ BT_GATT_CHRC_WRITE | BT_GATT_CHRC_WRITE_WITHOUT_RESP | BT_GATT_CHRC_NOTIFY, \ BT_GATT_PERM_WRITE_ENCRYPT, \ NULL, write_search_control_point, NULL), \ BT_AUDIO_CCC(search_control_point_cfg_changed), \ BT_AUDIO_CHRC(BT_UUID_MCS_SEARCH_RESULTS_OBJ_ID, \ BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY, \ BT_GATT_PERM_READ_ENCRYPT, \ read_search_results_id, NULL, NULL), \ BT_AUDIO_CCC(search_results_id_cfg_changed), #else #define ICON_OBJ_ID_CHARACTERISTIC_IF_OTS #define SEGMENTS_TRACK_GROUP_ID_CHARACTERISTICS_IF_OTS #define SEARCH_CHARACTERISTICS_IF_OTS #endif /* CONFIG_BT_OTS */ /* Media control service attributes */ #define BT_MCS_SERVICE_DEFINITION \ BT_GATT_PRIMARY_SERVICE(BT_UUID_GMCS), \ BT_GATT_INCLUDE_SERVICE(NULL), /* To be overwritten */ \ BT_AUDIO_CHRC(BT_UUID_MCS_PLAYER_NAME, \ BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY, \ BT_GATT_PERM_READ_ENCRYPT, \ read_player_name, NULL, NULL), \ BT_AUDIO_CCC(player_name_cfg_changed), \ ICON_OBJ_ID_CHARACTERISTIC_IF_OTS \ BT_AUDIO_CHRC(BT_UUID_MCS_ICON_URL, \ BT_GATT_CHRC_READ, \ BT_GATT_PERM_READ_ENCRYPT, \ read_icon_url, NULL, NULL), \ BT_AUDIO_CHRC(BT_UUID_MCS_TRACK_CHANGED, \ BT_GATT_CHRC_NOTIFY, \ BT_GATT_PERM_NONE, \ NULL, NULL, NULL), \ BT_AUDIO_CCC(track_cfg_changed), \ BT_AUDIO_CHRC(BT_UUID_MCS_TRACK_TITLE, \ BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY, \ BT_GATT_PERM_READ_ENCRYPT, \ read_track_title, NULL, NULL), \ BT_AUDIO_CCC(track_title_cfg_changed), \ BT_AUDIO_CHRC(BT_UUID_MCS_TRACK_DURATION, \ BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY, \ BT_GATT_PERM_READ_ENCRYPT, \ read_track_duration, NULL, NULL), \ BT_AUDIO_CCC(track_duration_cfg_changed), \ BT_AUDIO_CHRC(BT_UUID_MCS_TRACK_POSITION, \ BT_GATT_CHRC_READ | BT_GATT_CHRC_WRITE | \ BT_GATT_CHRC_WRITE_WITHOUT_RESP | BT_GATT_CHRC_NOTIFY, \ BT_GATT_PERM_READ_ENCRYPT | BT_GATT_PERM_WRITE_ENCRYPT, \ read_track_position, write_track_position, NULL), \ BT_AUDIO_CCC(track_position_cfg_changed), \ BT_AUDIO_CHRC(BT_UUID_MCS_PLAYBACK_SPEED, \ BT_GATT_CHRC_READ | BT_GATT_CHRC_WRITE | \ BT_GATT_CHRC_WRITE_WITHOUT_RESP | BT_GATT_CHRC_NOTIFY, \ BT_GATT_PERM_READ_ENCRYPT | BT_GATT_PERM_WRITE_ENCRYPT, \ read_playback_speed, write_playback_speed, NULL), \ BT_AUDIO_CCC(playback_speed_cfg_changed), \ BT_AUDIO_CHRC(BT_UUID_MCS_SEEKING_SPEED, \ BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY, \ BT_GATT_PERM_READ_ENCRYPT, \ read_seeking_speed, NULL, NULL), \ BT_AUDIO_CCC(seeking_speed_cfg_changed), \ SEGMENTS_TRACK_GROUP_ID_CHARACTERISTICS_IF_OTS \ BT_AUDIO_CHRC(BT_UUID_MCS_PLAYING_ORDER, \ BT_GATT_CHRC_READ | BT_GATT_CHRC_WRITE | \ BT_GATT_CHRC_WRITE_WITHOUT_RESP | BT_GATT_CHRC_NOTIFY, \ BT_GATT_PERM_READ_ENCRYPT | BT_GATT_PERM_WRITE_ENCRYPT, \ read_playing_order, write_playing_order, NULL), \ BT_AUDIO_CCC(playing_order_cfg_changed), \ BT_AUDIO_CHRC(BT_UUID_MCS_PLAYING_ORDERS, \ BT_GATT_CHRC_READ, \ BT_GATT_PERM_READ_ENCRYPT, \ read_playing_orders_supported, NULL, NULL), \ BT_AUDIO_CHRC(BT_UUID_MCS_MEDIA_STATE, \ BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY, \ BT_GATT_PERM_READ_ENCRYPT, \ read_media_state, NULL, NULL), \ BT_AUDIO_CCC(media_state_cfg_changed), \ BT_AUDIO_CHRC(BT_UUID_MCS_MEDIA_CONTROL_POINT, \ BT_GATT_CHRC_WRITE | BT_GATT_CHRC_WRITE_WITHOUT_RESP | BT_GATT_CHRC_NOTIFY, \ BT_GATT_PERM_WRITE_ENCRYPT, \ NULL, write_control_point, NULL), \ BT_AUDIO_CCC(control_point_cfg_changed), \ BT_AUDIO_CHRC(BT_UUID_MCS_MEDIA_CONTROL_OPCODES, \ BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY, \ BT_GATT_PERM_READ_ENCRYPT, \ read_opcodes_supported, NULL, NULL), \ BT_AUDIO_CCC(opcodes_supported_cfg_changed), \ SEARCH_CHARACTERISTICS_IF_OTS \ BT_AUDIO_CHRC(BT_UUID_CCID, \ BT_GATT_CHRC_READ, \ BT_GATT_PERM_READ_ENCRYPT, \ read_content_ctrl_id, NULL, NULL) static struct bt_gatt_attr svc_attrs[] = { BT_MCS_SERVICE_DEFINITION }; static struct bt_gatt_service mcs; #ifdef CONFIG_BT_OTS static struct bt_ots *ots; #endif /* CONFIG_BT_OTS */ #ifdef CONFIG_BT_OTS struct bt_ots *bt_mcs_get_ots(void) { return ots; } #endif /* CONFIG_BT_OTS */ /* Callback functions from the media player, notifying attributes */ /* Placed here, after the service definition, because they reference it. */ /* Helper function to notify non-string values */ static void notify(const struct bt_uuid *uuid, const void *data, uint16_t len) { int err = bt_gatt_notify_uuid(NULL, uuid, mcs.attrs, data, len); if (err) { if (err == -ENOTCONN) { LOG_DBG("Notification error: ENOTCONN (%d)", err); } else { LOG_ERR("Notification error: %d", err); } } } static void notify_string(struct bt_conn *conn, const struct bt_uuid *uuid, const char *str) { const uint16_t max_ntf_size = bt_audio_get_max_ntf_size(conn); int err; /* Send notification potentially truncated to the MTU */ err = bt_gatt_notify_uuid(conn, uuid, mcs.attrs, (void *)str, MIN(strlen(str), max_ntf_size)); if (err != 0) { LOG_ERR("Notification error: %d", err); } } static void mark_icon_url_changed_cb(struct bt_conn *conn, void *data) { struct client_state *client = &clients[bt_conn_index(conn)]; struct bt_conn_info info; int err; err = bt_conn_get_info(conn, &info); if (err != 0) { LOG_ERR("Failed to get conn info: %d", err); return; } if (info.state != BT_CONN_STATE_CONNECTED) { /* Not connected */ return; } atomic_set_bit(client->flags, FLAG_ICON_URL_CHANGED); } static void notify_cb(struct bt_conn *conn, void *data) { struct client_state *client = &clients[bt_conn_index(conn)]; struct bt_conn_info info; int err; err = bt_conn_get_info(conn, &info); if (err != 0) { LOG_ERR("Failed to get conn info: %d", err); return; } if (info.state != BT_CONN_STATE_CONNECTED) { /* Not connected */ return; } if (atomic_test_and_clear_bit(client->flags, FLAG_PLAYER_NAME_CHANGED)) { const char *name = media_proxy_sctrl_get_player_name(); LOG_DBG("Notifying player name: %s", name); notify_string(conn, BT_UUID_MCS_PLAYER_NAME, name); } if (atomic_test_and_clear_bit(client->flags, FLAG_TRACK_TITLE_CHANGED)) { const char *title = media_proxy_sctrl_get_track_title(); LOG_DBG("Notifying track title: %s", title); notify_string(conn, BT_UUID_MCS_TRACK_TITLE, title); } if (atomic_test_and_clear_bit(client->flags, FLAG_TRACK_DURATION_CHANGED)) { int32_t duration = media_proxy_sctrl_get_track_duration(); int32_t duration_le = sys_cpu_to_le32(duration); LOG_DBG("Notifying track duration: %d", duration); notify(BT_UUID_MCS_TRACK_DURATION, &duration_le, sizeof(duration_le)); } if (atomic_test_and_clear_bit(client->flags, FLAG_TRACK_POSITION_CHANGED)) { int32_t position = media_proxy_sctrl_get_track_position(); int32_t position_le = sys_cpu_to_le32(position); LOG_DBG("Notifying track position: %d", position); notify(BT_UUID_MCS_TRACK_POSITION, &position_le, sizeof(position_le)); } if (atomic_test_and_clear_bit(client->flags, FLAG_PLAYBACK_SPEED_CHANGED)) { int8_t speed = media_proxy_sctrl_get_playback_speed(); LOG_DBG("Notifying playback speed: %d", speed); notify(BT_UUID_MCS_PLAYBACK_SPEED, &speed, sizeof(speed)); } if (atomic_test_and_clear_bit(client->flags, FLAG_SEEKING_SPEED_CHANGED)) { int8_t speed = media_proxy_sctrl_get_seeking_speed(); LOG_DBG("Notifying seeking speed: %d", speed); notify(BT_UUID_MCS_SEEKING_SPEED, &speed, sizeof(speed)); } #if defined(CONFIG_BT_OTS) if (atomic_test_and_clear_bit(client->flags, FLAG_CURRENT_TRACK_OBJ_ID_CHANGED)) { uint64_t track_id = media_proxy_sctrl_get_current_track_id(); uint8_t track_id_le[BT_OTS_OBJ_ID_SIZE]; sys_put_le48(track_id, track_id_le); LOG_DBG_OBJ_ID("Notifying current track ID: ", track_id); notify(BT_UUID_MCS_CURRENT_TRACK_OBJ_ID, track_id_le, sizeof(track_id_le)); } if (atomic_test_and_clear_bit(client->flags, FLAG_NEXT_TRACK_OBJ_ID_CHANGED)) { uint64_t track_id = media_proxy_sctrl_get_next_track_id(); if (track_id == MPL_NO_TRACK_ID) { /* "If the media player has no next track, the length of the * characteristic shall be zero." */ LOG_DBG_OBJ_ID("Notifying EMPTY next track ID: ", track_id); notify(BT_UUID_MCS_NEXT_TRACK_OBJ_ID, NULL, 0); } else { uint8_t track_id_le[BT_OTS_OBJ_ID_SIZE]; sys_put_le48(track_id, track_id_le); LOG_DBG_OBJ_ID("Notifying next track ID: ", track_id); notify(BT_UUID_MCS_NEXT_TRACK_OBJ_ID, track_id_le, sizeof(track_id_le)); } } if (atomic_test_and_clear_bit(client->flags, FLAG_PARENT_GROUP_OBJ_ID_CHANGED)) { uint64_t group_id = media_proxy_sctrl_get_parent_group_id(); uint8_t group_id_le[BT_OTS_OBJ_ID_SIZE]; sys_put_le48(group_id, group_id_le); LOG_DBG_OBJ_ID("Notifying parent group ID: ", group_id); notify(BT_UUID_MCS_PARENT_GROUP_OBJ_ID, &group_id_le, sizeof(group_id_le)); } if (atomic_test_and_clear_bit(client->flags, FLAG_CURRENT_GROUP_OBJ_ID_CHANGED)) { uint64_t group_id = media_proxy_sctrl_get_current_group_id(); uint8_t group_id_le[BT_OTS_OBJ_ID_SIZE]; sys_put_le48(group_id, group_id_le); LOG_DBG_OBJ_ID("Notifying current group ID: ", group_id); notify(BT_UUID_MCS_CURRENT_GROUP_OBJ_ID, &group_id_le, sizeof(group_id_le)); } #endif /* CONFIG_BT_OTS */ if (atomic_test_and_clear_bit(client->flags, FLAG_TRACK_CHANGED)) { LOG_DBG("Notifying track change"); notify(BT_UUID_MCS_TRACK_CHANGED, NULL, 0); } if (atomic_test_and_clear_bit(client->flags, FLAG_PLAYING_ORDER_CHANGED)) { uint8_t order = media_proxy_sctrl_get_playing_order(); LOG_DBG("Notifying playing order: %d", order); notify(BT_UUID_MCS_PLAYING_ORDER, &order, sizeof(order)); } if (atomic_test_and_clear_bit(client->flags, FLAG_MEDIA_STATE_CHANGED)) { uint8_t state = media_proxy_sctrl_get_media_state(); LOG_DBG("Notifying media state: %d", state); notify(BT_UUID_MCS_MEDIA_STATE, &state, sizeof(state)); } if (atomic_test_and_clear_bit(client->flags, FLAG_MEDIA_CONTROL_OPCODES_CHANGED)) { uint32_t opcodes = media_proxy_sctrl_get_commands_supported(); uint32_t opcodes_le = sys_cpu_to_le32(opcodes); LOG_DBG("Notifying command opcodes supported: %d (0x%08x)", opcodes, opcodes); notify(BT_UUID_MCS_MEDIA_CONTROL_OPCODES, &opcodes_le, sizeof(opcodes_le)); } #if defined(CONFIG_BT_OTS) if (atomic_test_and_clear_bit(client->flags, FLAG_SEARCH_RESULTS_OBJ_ID_CHANGED)) { uint64_t search_id = media_proxy_sctrl_get_search_results_id(); uint8_t search_id_le[BT_OTS_OBJ_ID_SIZE]; sys_put_le48(search_id, search_id_le); LOG_DBG_OBJ_ID("Notifying search results ID: ", search_id); notify(BT_UUID_MCS_SEARCH_RESULTS_OBJ_ID, &search_id_le, sizeof(search_id_le)); } if (atomic_test_and_clear_bit(client->flags, FLAG_SEARCH_CONTROL_POINT_RESULT)) { uint8_t result_code = client->search_control_point_result; LOG_DBG("Notifying search control point - result: %d", result_code); notify(BT_UUID_MCS_SEARCH_CONTROL_POINT, &result_code, sizeof(result_code)); } #endif /* CONFIG_BT_OTS */ if (atomic_test_and_clear_bit(client->flags, FLAG_MEDIA_CONTROL_POINT_RESULT)) { LOG_DBG("Notifying control point command - opcode: %d, result: %d", client->cmd_ntf.requested_opcode, client->cmd_ntf.result_code); notify(BT_UUID_MCS_MEDIA_CONTROL_POINT, &client->cmd_ntf, sizeof(client->cmd_ntf)); } } static void deferred_nfy_work_handler(struct k_work *work) { bt_conn_foreach(BT_CONN_TYPE_LE, notify_cb, NULL); } static K_WORK_DEFINE(deferred_nfy_work, deferred_nfy_work_handler); static void defer_value_ntf(struct bt_conn *conn, void *data) { struct client_state *client = &clients[bt_conn_index(conn)]; struct bt_conn_info info; int err; err = bt_conn_get_info(conn, &info); if (err != 0) { LOG_ERR("Failed to get conn info: %d", err); return; } if (info.state != BT_CONN_STATE_CONNECTED) { /* Not connected */ return; } atomic_set_bit(client->flags, POINTER_TO_UINT(data)); k_work_submit(&deferred_nfy_work); } static void media_proxy_sctrl_player_name_cb(const char *name) { bt_conn_foreach(BT_CONN_TYPE_LE, defer_value_ntf, UINT_TO_POINTER(FLAG_PLAYER_NAME_CHANGED)); } void media_proxy_sctrl_icon_url_cb(const char *name) { bt_conn_foreach(BT_CONN_TYPE_LE, mark_icon_url_changed_cb, NULL); } void media_proxy_sctrl_track_changed_cb(void) { bt_conn_foreach(BT_CONN_TYPE_LE, defer_value_ntf, UINT_TO_POINTER(FLAG_TRACK_CHANGED)); } void media_proxy_sctrl_track_title_cb(const char *title) { bt_conn_foreach(BT_CONN_TYPE_LE, defer_value_ntf, UINT_TO_POINTER(FLAG_TRACK_TITLE_CHANGED)); } void media_proxy_sctrl_track_position_cb(int32_t position) { bt_conn_foreach(BT_CONN_TYPE_LE, defer_value_ntf, UINT_TO_POINTER(FLAG_TRACK_POSITION_CHANGED)); } void media_proxy_sctrl_track_duration_cb(int32_t duration) { bt_conn_foreach(BT_CONN_TYPE_LE, defer_value_ntf, UINT_TO_POINTER(FLAG_TRACK_DURATION_CHANGED)); } void media_proxy_sctrl_playback_speed_cb(int8_t speed) { bt_conn_foreach(BT_CONN_TYPE_LE, defer_value_ntf, UINT_TO_POINTER(FLAG_PLAYBACK_SPEED_CHANGED)); } void media_proxy_sctrl_seeking_speed_cb(int8_t speed) { bt_conn_foreach(BT_CONN_TYPE_LE, defer_value_ntf, UINT_TO_POINTER(FLAG_SEEKING_SPEED_CHANGED)); } #if defined(CONFIG_BT_OTS) void media_proxy_sctrl_current_track_id_cb(uint64_t id) { bt_conn_foreach(BT_CONN_TYPE_LE, defer_value_ntf, UINT_TO_POINTER(FLAG_CURRENT_TRACK_OBJ_ID_CHANGED)); } void media_proxy_sctrl_next_track_id_cb(uint64_t id) { bt_conn_foreach(BT_CONN_TYPE_LE, defer_value_ntf, UINT_TO_POINTER(FLAG_NEXT_TRACK_OBJ_ID_CHANGED)); } void media_proxy_sctrl_parent_group_id_cb(uint64_t id) { bt_conn_foreach(BT_CONN_TYPE_LE, defer_value_ntf, UINT_TO_POINTER(FLAG_PARENT_GROUP_OBJ_ID_CHANGED)); } void media_proxy_sctrl_current_group_id_cb(uint64_t id) { bt_conn_foreach(BT_CONN_TYPE_LE, defer_value_ntf, UINT_TO_POINTER(FLAG_CURRENT_GROUP_OBJ_ID_CHANGED)); } #endif /* CONFIG_BT_OTS */ void media_proxy_sctrl_playing_order_cb(uint8_t order) { bt_conn_foreach(BT_CONN_TYPE_LE, defer_value_ntf, UINT_TO_POINTER(FLAG_PLAYING_ORDER_CHANGED)); } void media_proxy_sctrl_media_state_cb(uint8_t state) { bt_conn_foreach(BT_CONN_TYPE_LE, defer_value_ntf, UINT_TO_POINTER(FLAG_MEDIA_STATE_CHANGED)); } static void defer_media_control_point_ntf(struct bt_conn *conn, void *data) { struct client_state *client = &clients[bt_conn_index(conn)]; const struct mpl_cmd_ntf *cmd_ntf = data; struct bt_conn_info info; int err; err = bt_conn_get_info(conn, &info); if (err != 0) { LOG_ERR("Failed to get conn info: %d", err); return; } if (info.state != BT_CONN_STATE_CONNECTED) { /* Not connected */ return; } if (atomic_test_and_clear_bit(client->flags, FLAG_MEDIA_CONTROL_POINT_BUSY)) { client->cmd_ntf = *cmd_ntf; atomic_set_bit(client->flags, FLAG_MEDIA_CONTROL_POINT_RESULT); k_work_submit(&deferred_nfy_work); } } void media_proxy_sctrl_command_cb(const struct mpl_cmd_ntf *cmd_ntf) { /* FIXME: Control Point notification shall be sent to operation initiator only */ bt_conn_foreach(BT_CONN_TYPE_LE, defer_media_control_point_ntf, (void *)cmd_ntf); } void media_proxy_sctrl_commands_supported_cb(uint32_t opcodes) { bt_conn_foreach(BT_CONN_TYPE_LE, defer_value_ntf, UINT_TO_POINTER(FLAG_MEDIA_CONTROL_OPCODES_CHANGED)); } #if defined(CONFIG_BT_OTS) static void defer_search_control_point_ntf(struct bt_conn *conn, void *data) { struct client_state *client = &clients[bt_conn_index(conn)]; struct bt_conn_info info; int err; err = bt_conn_get_info(conn, &info); if (err != 0) { LOG_ERR("Failed to get conn info: %d", err); return; } if (info.state != BT_CONN_STATE_CONNECTED) { /* Not connected */ return; } if (atomic_test_and_clear_bit(client->flags, FLAG_SEARCH_CONTROL_POINT_BUSY)) { client->search_control_point_result = POINTER_TO_UINT(data); atomic_set_bit(client->flags, FLAG_SEARCH_CONTROL_POINT_RESULT); k_work_submit(&deferred_nfy_work); } } void media_proxy_sctrl_search_cb(uint8_t result_code) { /* FIXME: Control Point notification shall be sent to operation initiator only */ bt_conn_foreach(BT_CONN_TYPE_LE, defer_search_control_point_ntf, UINT_TO_POINTER(result_code)); } void media_proxy_sctrl_search_results_id_cb(uint64_t id) { bt_conn_foreach(BT_CONN_TYPE_LE, defer_value_ntf, UINT_TO_POINTER(FLAG_SEARCH_RESULTS_OBJ_ID_CHANGED)); } #endif /* CONFIG_BT_OTS */ /* Register the service */ int bt_mcs_init(struct bt_ots_cb *ots_cbs) { static bool initialized; int err; if (initialized) { LOG_DBG("Already initialized"); return -EALREADY; } mcs = (struct bt_gatt_service)BT_GATT_SERVICE(svc_attrs); #ifdef CONFIG_BT_OTS struct bt_ots_init_param ots_init; ots = bt_ots_free_instance_get(); if (!ots) { LOG_ERR("Failed to retrieve OTS instance\n"); return -ENOMEM; } /* Configure OTS initialization. */ memset(&ots_init, 0, sizeof(ots_init)); BT_OTS_OACP_SET_FEAT_READ(ots_init.features.oacp); BT_OTS_OLCP_SET_FEAT_GO_TO(ots_init.features.olcp); ots_init.cb = ots_cbs; /* Initialize OTS instance. */ err = bt_ots_init(ots, &ots_init); if (err) { LOG_ERR("Failed to init OTS (err:%d)\n", err); return err; } /* TODO: Maybe the user_data pointer can be in a different way */ for (int i = 0; i < mcs.attr_count; i++) { if (!bt_uuid_cmp(mcs.attrs[i].uuid, BT_UUID_GATT_INCLUDE)) { mcs.attrs[i].user_data = bt_ots_svc_decl_get(ots); } } #endif /* CONFIG_BT_OTS */ err = bt_gatt_service_register(&mcs); if (err) { LOG_ERR("Could not register the MCS service"); #ifdef CONFIG_BT_OTS /* TODO: How does one un-register the OTS? */ #endif /* CONFIG_BT_OTS */ return -ENOEXEC; } /* Set up the callback structure */ cbs.player_name = media_proxy_sctrl_player_name_cb; cbs.icon_url = media_proxy_sctrl_icon_url_cb; cbs.track_changed = media_proxy_sctrl_track_changed_cb; cbs.track_title = media_proxy_sctrl_track_title_cb; cbs.track_duration = media_proxy_sctrl_track_duration_cb; cbs.track_position = media_proxy_sctrl_track_position_cb; cbs.playback_speed = media_proxy_sctrl_playback_speed_cb; cbs.seeking_speed = media_proxy_sctrl_seeking_speed_cb; #ifdef CONFIG_BT_OTS cbs.current_track_id = media_proxy_sctrl_current_track_id_cb; cbs.next_track_id = media_proxy_sctrl_next_track_id_cb; cbs.parent_group_id = media_proxy_sctrl_parent_group_id_cb; cbs.current_group_id = media_proxy_sctrl_current_group_id_cb; #endif /* CONFIG_BT_OTS */ cbs.playing_order = media_proxy_sctrl_playing_order_cb; cbs.media_state = media_proxy_sctrl_media_state_cb; cbs.command = media_proxy_sctrl_command_cb; cbs.commands_supported = media_proxy_sctrl_commands_supported_cb; #ifdef CONFIG_BT_OTS cbs.search = media_proxy_sctrl_search_cb; cbs.search_results_id = media_proxy_sctrl_search_results_id_cb; #endif /* CONFIG_BT_OTS */ media_proxy_sctrl_register(&cbs); initialized = true; return 0; }