From 6f94d0246369298a38e38b3e70d74d354c90596f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Asbj=C3=B8rn=20S=C3=A6b=C3=B8?= Date: Fri, 22 Oct 2021 09:31:23 +0200 Subject: [PATCH] Bluetooth: Audio: Media control client MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit adds the Media Control Client from the topic-le-audio branch. This is a part of the upmerge of the le-audio media control files. This client has been developed and maintained over a couple of years now, and is mature. During the development it has passed both IOP-testing and PTS qualification testing. The commit is a pure copy of the files and content in the topic-le-audio branch, with the following exceptions: - files are in bluetooth/audio instead of bluetooth/host/audio, with some include paths updated as a consequence - as a consequence, CMake files and Kconfig files updates are done in other locations - a macro for debug output of Object ID values has been (temporarily) added to mcc.h, to avoid a dependency - a blank line added after a declaration and an overlong line split, to pass check_compliance - copyrights have been updated Signed-off-by: Asbjørn Sæbø --- include/bluetooth/audio/mcc.h | 938 +++++++++ subsys/bluetooth/audio/CMakeLists.txt | 2 + subsys/bluetooth/audio/Kconfig | 1 + subsys/bluetooth/audio/Kconfig.mcs | 207 ++ subsys/bluetooth/audio/mcc.c | 2774 +++++++++++++++++++++++++ 5 files changed, 3922 insertions(+) create mode 100644 include/bluetooth/audio/mcc.h create mode 100644 subsys/bluetooth/audio/Kconfig.mcs create mode 100644 subsys/bluetooth/audio/mcc.c diff --git a/include/bluetooth/audio/mcc.h b/include/bluetooth/audio/mcc.h new file mode 100644 index 00000000000..d93f66f65c4 --- /dev/null +++ b/include/bluetooth/audio/mcc.h @@ -0,0 +1,938 @@ +/** + * @brief Bluetooth Media Control Client (MCC) interface + * + * Updated to the Media Control Profile specification revision 1.0 + * + * @defgroup bt_gatt_mcc Media Control Client (MCC) + * + * @ingroup bluetooth + * @{ + * + * [Experimental] Users should note that the APIs can change + * as a part of ongoing development. + */ + +/* + * Copyright (c) 2019 - 2021 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_INCLUDE_BLUETOOTH_AUDIO_MCC_ +#define ZEPHYR_INCLUDE_BLUETOOTH_AUDIO_MCC_ + +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/**** Callback functions ******************************************************/ + +/** + * @brief Callback function for bt_mcc_discover_mcs() + * + * Called when a media control server is discovered + * + * @param conn The connection that was used to initialise the media control client + * @param err Error value. 0 on success, GATT error or errno on fail + */ +typedef void (*bt_mcc_discover_mcs_cb)(struct bt_conn *conn, int err); + +/** + * @brief Callback function for bt_mcc_read_player_name() + * + * Called when the player name is read or notified + * + * @param conn The connection that was used to initialise the media control client + * @param err Error value. 0 on success, GATT error or errno on fail + * @param name Player name + */ +typedef void (*bt_mcc_read_player_name_cb)(struct bt_conn *conn, int err, const char *name); + +#ifdef CONFIG_BT_OTC +/** + * @brief Callback function for bt_mcc_read_icon_obj_id() + * + * Called when the icon object ID is read + * + * @param conn The connection that was used to initialise the media control client + * @param err Error value. 0 on success, GATT error or errno on fail + * @param icon_id The ID of the Icon Object. This is a UINT48 in a uint64_t + */ +typedef void (*bt_mcc_read_icon_obj_id_cb)(struct bt_conn *conn, int err, uint64_t id); +#endif /* CONFIG_BT_OTC */ + +/** + * @brief Callback function for bt_mcc_read_icon_url() + * + * Called when the icon URL is read + * + * @param conn The connection that was used to initialise the media control client + * @param err Error value. 0 on success, GATT error or errno on fail + * @param icon_url The URL of the Icon + */ +typedef void (*bt_mcc_read_icon_url_cb)(struct bt_conn *conn, int err, const char *icon_url); + +/** + * @brief Callback function for track changed notifications + * + * Called when a track change is notified. + * + * The track changed characteristic is a special case. It can not be read or + * set, it can only be notified. + * + * @param conn The connection that was used to initialise the media control client + * @param err Error value. 0 on success, GATT error or errno on fail + */ +typedef void (*bt_mcc_track_changed_ntf_cb)(struct bt_conn *conn, int err); + + +/** + * @brief Callback function for bt_mcc_read_track_title() + * + * Called when the track title is read or notified + * + * @param conn The connection that was used to initialise the media control client + * @param err Error value. 0 on success, GATT error or errno on fail + * @param title The title of the track + */ +typedef void (*bt_mcc_read_track_title_cb)(struct bt_conn *conn, int err, const char *title); + +/** + * @brief Callback function for bt_mcc_read_track_duration() + * + * Called when the track duration is read or notified + * + * @param conn The connection that was used to initialise the media control client + * @param err Error value. 0 on success, GATT error or errno on fail + * @param dur The duration of the track + */ +typedef void (*bt_mcc_read_track_duration_cb)(struct bt_conn *conn, int err, int32_t dur); + +/** + * @brief Callback function for bt_mcc_read_track_position() + * + * Called when the track position is read or notified + * + * @param conn The connection that was used to initialise the media control client + * @param err Error value. 0 on success, GATT error or errno on fail + * @param pos The Track Position + */ +typedef void (*bt_mcc_read_track_position_cb)(struct bt_conn *conn, int err, int32_t pos); + +/** + * @brief Callback function for bt_mcc_set_track_position() + * + * Called when the track position is set + * + * @param conn The connection that was used to initialise the media control client + * @param err Error value. 0 on success, GATT error or errno on fail + * @param pos The Track Position set (or attempted to set) + */ +typedef void (*bt_mcc_set_track_position_cb)(struct bt_conn *conn, int err, int32_t pos); + +/** + * @brief Callback function for bt_mcc_read_playback_speed() + * + * Called when the playback speed is read or notified + * + * @param conn The connection that was used to initialise the media control client + * @param err Error value. 0 on success, GATT error or errno on fail + * @param speed The Playback Speed + */ +typedef void (*bt_mcc_read_playback_speed_cb)(struct bt_conn *conn, int err, int8_t speed); + +/** + * @brief Callback function for bt_mcc_set_playback_speed() + * + * Called when the playback speed is set + * + * @param conn The connection that was used to initialise the media control client + * @param err Error value. 0 on success, GATT error or errno on fail + * @param speed The Playback Speed set (or attempted to set) + */ +typedef void (*bt_mcc_set_playback_speed_cb)(struct bt_conn *conn, int err, int8_t speed); + +/** + * @brief Callback function for bt_mcc_read_seeking_speed() + * + * Called when the seeking speed is read or notified + * + * @param conn The connection that was used to initialise the media control client + * @param err Error value. 0 on success, GATT error or errno on fail + * @param speed The Seeking Speed + */ +typedef void (*bt_mcc_read_seeking_speed_cb)(struct bt_conn *conn, int err, int8_t speed); + +#ifdef CONFIG_BT_OTC +/** + * @brief Callback function for bt_mcc_read_segments_obj_id() + * + * Called when the track segments object ID is read + * + * @param conn The connection that was used to initialise the media control client + * @param err Error value. 0 on success, GATT error or errno on fail + * @param id The Track Segments Object ID (UINT48) + */ +typedef void (*bt_mcc_read_segments_obj_id_cb)(struct bt_conn *conn, int err, uint64_t id); + +/** + * @brief Callback function for bt_mcc_read_current_track_obj_id() + * + * Called when the current track object ID is read or notified + * + * @param conn The connection that was used to initialise the media control client + * @param err Error value. 0 on success, GATT error or errno on fail + * @param id The Current Track Object ID (UINT48) + */ +typedef void (*bt_mcc_read_current_track_obj_id_cb)(struct bt_conn *conn, int err, uint64_t id); + +/** + * @brief Callback function for bt_mcc_set_current_track_obj_id() + * + * Called when the current track object ID is set + * + * @param conn The connection that was used to initialise the media control client + * @param err Error value. 0 on success, GATT error or errno on fail + * @param id The Object ID (UINT48) set (or attempted to set) + */ +typedef void (*bt_mcc_set_current_track_obj_id_cb)(struct bt_conn *conn, int err, uint64_t id); + +/** + * @brief Callback function for bt_mcc_read_next_track_obj_id_obj() + * + * Called when the next track object ID is read or notified + * + * @param conn The connection that was used to initialise the media control client + * @param err Error value. 0 on success, GATT error or errno on fail + * @param id The Next Track Object ID (UINT48) + */ +typedef void (*bt_mcc_read_next_track_obj_id_cb)(struct bt_conn *conn, int err, uint64_t id); + +/** + * @brief Callback function for bt_mcc_set_next_track_obj_id() + * + * Called when the next track object ID is set + * + * @param conn The connection that was used to initialise the media control client + * @param err Error value. 0 on success, GATT error or errno on fail + * @param id The Object ID (UINT48) set (or attempted to set) + */ +typedef void (*bt_mcc_set_next_track_obj_id_cb)(struct bt_conn *conn, int err, uint64_t id); + +/** + * @brief Callback function for bt_mcc_read_parent_group_obj_id() + * + * Called when the parent group object ID is read or notified + * + * @param conn The connection that was used to initialise the media control client + * @param err Error value. 0 on success, GATT error or errno on fail + * @param id The Parent Group Object ID (UINT48) + */ +typedef void (*bt_mcc_read_parent_group_obj_id_cb)(struct bt_conn *conn, int err, uint64_t id); + +/** + * @brief Callback function for bt_mcc_read_current_group_obj_id() + * + * Called when the current group object ID is read or notified + * + * @param conn The connection that was used to initialise the media control client + * @param err Error value. 0 on success, GATT error or errno on fail + * @param id The Current Group Object ID (UINT48) + */ +typedef void (*bt_mcc_read_current_group_obj_id_cb)(struct bt_conn *conn, int err, uint64_t id); + +/** + * @brief Callback function for bt_mcc_set_current_group_obj_id() + * + * Called when the current group object ID is set + * + * @param conn The connection that was used to initialise the media control client + * @param err Error value. 0 on success, GATT error or errno on fail + * @param id The Object ID (UINT48) set (or attempted to set) + */ +typedef void (*bt_mcc_set_current_group_obj_id_cb)(struct bt_conn *conn, int err, uint64_t obj_id); + +#endif /* CONFIG_BT_OTC */ + +/** + * @brief Callback function for bt_mcc_read_playing_order() + * + * Called when the playing order is read or notified + * + * @param conn The connection that was used to initialise the media control client + * @param err Error value. 0 on success, GATT error or errno on fail + * @param order The playback order + */ +typedef void (*bt_mcc_read_playing_order_cb)(struct bt_conn *conn, int err, uint8_t order); + +/** + * @brief Callback function for bt_mcc_set_playing_order() + * + * Called when the playing order is set + * + * @param conn The connection that was used to initialise the media control client + * @param err Error value. 0 on success, GATT error or errno on fail + * @param order The Playing Order set (or attempted to set) + */ +typedef void (*bt_mcc_set_playing_order_cb)(struct bt_conn *conn, int err, uint8_t order); + +/** + * @brief Callback function for bt_mcc_read_playing_orders_supported() + * + * Called when the supported playing orders are read or notified + * + * @param conn The connection that was used to initialise the media control client + * @param err Error value. 0 on success, GATT error or errno on fail + * @param orders The playing orders supported (bitmap) + */ +typedef void (*bt_mcc_read_playing_orders_supported_cb)(struct bt_conn *conn, int err, + uint16_t orders); + +/** + * @brief Callback function for bt_mcc_read_media_state() + * + * Called when the media state is read or notified + * + * @param conn The connection that was used to initialise the media control client + * @param err Error value. 0 on success, GATT error or errno on fail + * @param state The Media State + */ +typedef void (*bt_mcc_read_media_state_cb)(struct bt_conn *conn, int err, uint8_t state); + +/** + * @brief Callback function for bt_mcc_send_cmd() + * + * Called when a command is sent, i.e. when the media control point is set + * + * @param conn The connection that was used to initialise the media control client + * @param err Error value. 0 on success, GATT error or errno on fail + * @param cmd The command sent + */ +typedef void (*bt_mcc_send_cmd_cb)(struct bt_conn *conn, int err, struct mpl_cmd cmd); + +/** + * @brief Callback function for command notifications + * + * Called when the media control point is notified + * + * Notifications for commands (i.e. for writes to the media control point) use a + * different parameter structure than what is used for sending commands (writing + * to the media control point) + * + * @param conn The connection that was used to initialise the media control client + * @param err Error value. 0 on success, GATT error or errno on fail + * @param ntf The command notification + */ +typedef void (*bt_mcc_cmd_ntf_cb)(struct bt_conn *conn, int err, struct mpl_cmd_ntf ntf); + +/** + * @brief Callback function for bt_mcc_read_opcodes_supported() + * + * Called when the supported opcodes (commands) are read or notified + * + * @param conn The connection that was used to initialise the media control client + * @param err Error value. 0 on success, GATT error or errno on fail + * @param opcodes The supported opcodes + */ +typedef void (*bt_mcc_read_opcodes_supported_cb)(struct bt_conn *conn, int err, + uint32_t opcodes); + +#ifdef CONFIG_BT_OTC +/** + * @brief Callback function for bt_mcc_send_search() + * + * Called when a search is sent, i.e. when the search control point is set + * + * @param conn The connection that was used to initialise the media control client + * @param err Error value. 0 on success, GATT error or errno on fail + * @param search The search set (or attempted to set) + */ +typedef void (*bt_mcc_send_search_cb)(struct bt_conn *conn, int err, + struct mpl_search search); + +/** + * @brief Callback function for search notifications + * + * Called when the search control point is notified + * + * Notifications for searches (i.e. for writes to the search control point) use a + * different parameter structure than what is used for sending searches (writing + * to the search control point) + * + * @param conn The connection that was used to initialise the media control client + * @param err Error value. 0 on success, GATT error or errno on fail + * @param ntf The search notification + */ +typedef void (*bt_mcc_search_ntf_cb)(struct bt_conn *conn, int err, + uint8_t result_code); + +/** + * @brief Callback function for bt_mcc_read_search_results_obj_id() + * + * Called when the search results object ID is read or notified + * + * Note that the Search Results Object ID value may be zero, in case the + * characteristic does not exist on the server. (This will be the case if + * there has not been a successful search.) + * + * @param conn The connection that was used to initialise the media control client + * @param err Error value. 0 on success, GATT error or errno on fail + * @param id The Search Results Object ID (UINT48) + */ +typedef void (*bt_mcc_read_search_results_obj_id_cb)(struct bt_conn *conn, + int err, uint64_t id); +#endif /* CONFIG_BT_OTC */ + +/** + * @brief Callback function for bt_mcc_read_content_control_id() + * + * Called when the content control ID is read + * + * @param conn The connection that was used to initialise the media control client + * @param err Error value. 0 on success, GATT error or errno on fail + * @param ccid The Content Control ID + */ +typedef void (*bt_mcc_read_content_control_id_cb)(struct bt_conn *conn, + int err, uint8_t ccid); +#ifdef CONFIG_BT_OTC +/**** Callback functions for the included Object Transfer service *************/ + +/** + * @brief Callback function for object selected + * + * Called when an object is selected + * + * @param conn The connection that was used to initialise the media control client + * @param err Error value. 0 on success, GATT error or errno on fail + */ +typedef void (*bt_mcc_otc_obj_selected_cb)(struct bt_conn *conn, int err); + +/** + * @brief Callback function for bt_mcc_otc_read_object_meatadata() + * + * Called when object metadata is read + * + * @param conn The connection that was used to initialise the media control client + * @param err Error value. 0 on success, GATT error or errno on fail + */ +typedef void (*bt_mcc_otc_obj_metadata_cb)(struct bt_conn *conn, int err); + +/** + * @brief Callback function for bt_mcc_otc_read_icon_object() + * + * Called when the icon object is read + * + * @param conn The connection that was used to initialise the media control client + * @param err Error value. 0 on success, GATT error or errno on fail + * @param buf Buffer containing the object contents + * + * If err is EMSGSIZE, the object contents have been truncated. + */ +typedef void (*bt_mcc_otc_read_icon_object_cb)(struct bt_conn *conn, int err, + struct net_buf_simple *buf); + +/** + * @brief Callback function for bt_mcc_otc_read_track_segments_object() + * + * Called when the track segments object is read + * + * @param conn The connection that was used to initialise the media control client + * @param err Error value. 0 on success, GATT error or errno on fail + * @param buf Buffer containing the object contents + * + * If err is EMSGSIZE, the object contents have been truncated. + */ +typedef void (*bt_mcc_otc_read_track_segments_object_cb)(struct bt_conn *conn, int err, + struct net_buf_simple *buf); + +/** + * @brief Callback function for bt_mcc_otc_read_current_track_object() + * + * Called when the current track object is read + * + * @param conn The connection that was used to initialise the media control client + * @param err Error value. 0 on success, GATT error or errno on fail + * @param buf Buffer containing the object contents + * + * If err is EMSGSIZE, the object contents have been truncated. + */ +typedef void (*bt_mcc_otc_read_current_track_object_cb)(struct bt_conn *conn, int err, + struct net_buf_simple *buf); + +/** + * @brief Callback function for bt_mcc_otc_read_next_track_object() + * + * Called when the next track object is read + * + * @param conn The connection that was used to initialise the media control client + * @param err Error value. 0 on success, GATT error or errno on fail + * @param buf Buffer containing the object contents + * + * If err is EMSGSIZE, the object contents have been truncated. + */ +typedef void (*bt_mcc_otc_read_next_track_object_cb)(struct bt_conn *conn, int err, + struct net_buf_simple *buf); + +/** + * @brief Callback function for bt_mcc_otc_read_parent_group_object() + * + * Called when the parent group object is read + * + * @param conn The connection that was used to initialise the media control client + * @param err Error value. 0 on success, GATT error or errno on fail + * @param buf Buffer containing the object contents + * + * If err is EMSGSIZE, the object contents have been truncated. + */ +typedef void (*bt_mcc_otc_read_parent_group_object_cb)(struct bt_conn *conn, int err, + struct net_buf_simple *buf); + +/** + * @brief Callback function for bt_mcc_otc_read_current_group_object() + * + * Called when the current group object is read + * + * @param conn The connection that was used to initialise the media control client + * @param err Error value. 0 on success, GATT error or errno on fail + * @param buf Buffer containing the object contents + * + * If err is EMSGSIZE, the object contents have been truncated. + */ +typedef void (*bt_mcc_otc_read_current_group_object_cb)(struct bt_conn *conn, int err, + struct net_buf_simple *buf); + +#endif /* CONFIG_BT_OTC */ + + +/** + * @brief Media control client callbacks + */ +struct bt_mcc_cb { + bt_mcc_discover_mcs_cb discover_mcs; + bt_mcc_read_player_name_cb read_player_name; +#ifdef CONFIG_BT_OTC + bt_mcc_read_icon_obj_id_cb read_icon_obj_id; +#endif /* CONFIG_BT_OTC */ + bt_mcc_read_icon_url_cb read_icon_url; + bt_mcc_track_changed_ntf_cb track_changed_ntf; + bt_mcc_read_track_title_cb read_track_title; + bt_mcc_read_track_duration_cb read_track_duration; + bt_mcc_read_track_position_cb read_track_position; + bt_mcc_set_track_position_cb set_track_position; + bt_mcc_read_playback_speed_cb read_playback_speed; + bt_mcc_set_playback_speed_cb set_playback_speed; + bt_mcc_read_seeking_speed_cb read_seeking_speed; +#ifdef CONFIG_BT_OTC + bt_mcc_read_segments_obj_id_cb read_segments_obj_id; + bt_mcc_read_current_track_obj_id_cb read_current_track_obj_id; + bt_mcc_set_current_track_obj_id_cb set_current_track_obj_id; + bt_mcc_read_next_track_obj_id_cb read_next_track_obj_id; + bt_mcc_set_next_track_obj_id_cb set_next_track_obj_id; + bt_mcc_read_current_group_obj_id_cb read_current_group_obj_id; + bt_mcc_set_current_group_obj_id_cb set_current_group_obj_id; + bt_mcc_read_parent_group_obj_id_cb read_parent_group_obj_id; +#endif /* CONFIG_BT_OTC */ + bt_mcc_read_playing_order_cb read_playing_order; + bt_mcc_set_playing_order_cb set_playing_order; + bt_mcc_read_playing_orders_supported_cb read_playing_orders_supported; + bt_mcc_read_media_state_cb read_media_state; + bt_mcc_send_cmd_cb send_cmd; + bt_mcc_cmd_ntf_cb cmd_ntf; + bt_mcc_read_opcodes_supported_cb read_opcodes_supported; +#ifdef CONFIG_BT_OTC + bt_mcc_send_search_cb send_search; + bt_mcc_search_ntf_cb search_ntf; + bt_mcc_read_search_results_obj_id_cb read_search_results_obj_id; +#endif /* CONFIG_BT_OTC */ + bt_mcc_read_content_control_id_cb read_content_control_id; +#ifdef CONFIG_BT_OTC + bt_mcc_otc_obj_selected_cb otc_obj_selected; + bt_mcc_otc_obj_metadata_cb otc_obj_metadata; + bt_mcc_otc_read_icon_object_cb otc_icon_object; + bt_mcc_otc_read_track_segments_object_cb otc_track_segments_object; + bt_mcc_otc_read_current_track_object_cb otc_current_track_object; + bt_mcc_otc_read_next_track_object_cb otc_next_track_object; + bt_mcc_otc_read_current_group_object_cb otc_current_group_object; + bt_mcc_otc_read_parent_group_object_cb otc_parent_group_object; +#endif /* CONFIG_BT_OTC */ +}; + +/**** Functions ***************************************************************/ + +/** + * @brief Initialize Media Control Client + * + * @param cb Callbacks to be used + * + * @return 0 if success, errno on failure. + */ +int bt_mcc_init(struct bt_mcc_cb *cb); + + +/** + * @brief Discover Media Control Service + * + * Discover Media Control Service (MCS) on the server given by the connection + * Optionally subscribe to notifications. + * + * Shall be called once, after media control client initialization and before + * using other media control client functionality. + * + * @param conn Connection to the peer device + * @param subscribe Whether to subscribe to notifications + * + * @return 0 if success, errno on failure. + */ +int bt_mcc_discover_mcs(struct bt_conn *conn, bool subscribe); + +/** + * @brief Read Media Player Name + * + * @param conn Connection to the peer device + * + * @return 0 if success, errno on failure. + */ +int bt_mcc_read_player_name(struct bt_conn *conn); + +#ifdef CONFIG_BT_OTC +/** + * @brief Read Icon Object ID + * + * @param conn Connection to the peer device + * + * @return 0 if success, errno on failure. + */ +int bt_mcc_read_icon_obj_id(struct bt_conn *conn); +#endif /* CONFIG_BT_OTC */ + +/** + * @brief Read Icon Object URL + * + * @param conn Connection to the peer device + * + * @return 0 if success, errno on failure. + */ +int bt_mcc_read_icon_url(struct bt_conn *conn); + +/** + * @brief Read Track Title + * + * @param conn Connection to the peer device + * + * @return 0 if success, errno on failure. + */ +int bt_mcc_read_track_title(struct bt_conn *conn); + +/** + * @brief Read Track Duration + * + * @param conn Connection to the peer device + * + * @return 0 if success, errno on failure. + */ +int bt_mcc_read_track_duration(struct bt_conn *conn); + +/** + * @brief Read Track Position + * + * @param conn Connection to the peer device + * + * @return 0 if success, errno on failure. + */ +int bt_mcc_read_track_position(struct bt_conn *conn); + +/** + * @brief Set Track position + * + * @param conn Connection to the peer device + * @param pos Track position + * + * @return 0 if success, errno on failure. + */ +int bt_mcc_set_track_position(struct bt_conn *conn, int32_t pos); + +/** + * @brief Read Playback speed + * + * @param conn Connection to the peer device + * + * @return 0 if success, errno on failure. + */ +int bt_mcc_read_playback_speed(struct bt_conn *conn); + +/** + * @brief Set Playback Speed + * + * @param conn Connection to the peer device + * @param speed Playback speed + * + * @return 0 if success, errno on failure. + */ +int bt_mcc_set_playback_speed(struct bt_conn *conn, int8_t speed); + +/** + * @brief Read Seeking speed + * + * @param conn Connection to the peer device + * + * @return 0 if success, errno on failure. + */ +int bt_mcc_read_seeking_speed(struct bt_conn *conn); + +#ifdef CONFIG_BT_OTC +/** + * @brief Read Track Segments Object ID + * + * @param conn Connection to the peer device + * + * @return 0 if success, errno on failure. + */ +int bt_mcc_read_segments_obj_id(struct bt_conn *conn); + +/** + * @brief Read Current Track Object ID + * + * @param conn Connection to the peer device + * + * @return 0 if success, errno on failure. + */ +int bt_mcc_read_current_track_obj_id(struct bt_conn *conn); + +/** + * @brief Set Current Track Object ID + * + * Set the Current Track to the the track given by the @p id parameter + * + * @param conn Connection to the peer device + * @param id Object Transfer Service ID (UINT48) of the track to set as the current track + * + * @return 0 if success, errno on failure. + */ +int bt_mcc_set_current_track_obj_id(struct bt_conn *conn, uint64_t id); + +/** + * @brief Read Next Track Object ID + * + * @param conn Connection to the peer device + * @param id Object Transfer Service ID (UINT48) of the track to set as the current track + * + * @return 0 if success, errno on failure. + */ +int bt_mcc_read_next_track_obj_id(struct bt_conn *conn); + +/** + * @brief Set Next Track Object ID + * + * Set the Next Track to the the track given by the @p id parameter + * + * @param conn Connection to the peer device + * @param id Object Transfer Service ID (UINT48) of the track to set as the next track + * + * @return 0 if success, errno on failure. + */ +int bt_mcc_set_next_track_obj_id(struct bt_conn *conn, uint64_t id); + +/** + * @brief Read Current Group Object ID + * + * @param conn Connection to the peer device + * + * @return 0 if success, errno on failure. + */ +int bt_mcc_read_current_group_obj_id(struct bt_conn *conn); + +/** + * @brief Set Current Group Object ID + * + * Set the Current Group to the the group given by the @p id parameter + * + * @param conn Connection to the peer device + * @param id Object Transfer Service ID (UINT48) of the group to set as the current group + * + * @return 0 if success, errno on failure. + */ +int bt_mcc_set_current_group_obj_id(struct bt_conn *conn, uint64_t id); + +/** + * @brief Read Parent Group Object ID + * + * @param conn Connection to the peer device + * + * @return 0 if success, errno on failure. + */ +int bt_mcc_read_parent_group_obj_id(struct bt_conn *conn); +#endif /* CONFIG_BT_OTC */ + +/** + * @brief Read Playing Order + * + * @param conn Connection to the peer device + * + * @return 0 if success, errno on failure. + */ +int bt_mcc_read_playing_order(struct bt_conn *conn); + +/** + * @brief Set Playing Order + * + * @param conn Connection to the peer device + * @param order Playing order + * + * @return 0 if success, errno on failure. + */ +int bt_mcc_set_playing_order(struct bt_conn *conn, uint8_t order); + +/** + * @brief Read Playing Orders Supported + * + * @param conn Connection to the peer device + * + * @return 0 if success, errno on failure. + */ +int bt_mcc_read_playing_orders_supported(struct bt_conn *conn); + +/** + * @brief Read Media State + * + * @param conn Connection to the peer device + * + * @return 0 if success, errno on failure. + */ +int bt_mcc_read_media_state(struct bt_conn *conn); + +/** + * @brief Send a command + * + * Write a command (e.g. "play", "pause") to the server's media control point. + * + * @param conn Connection to the peer device + * @param cmd The command to send + * + * @return 0 if success, errno on failure. + */ +int bt_mcc_send_cmd(struct bt_conn *conn, struct mpl_cmd cmd); + +/** + * @brief Read Opcodes Supported + * + * @param conn Connection to the peer device + * + * @return 0 if success, errno on failure. + */ +int bt_mcc_read_opcodes_supported(struct bt_conn *conn); + +#ifdef CONFIG_BT_OTC +/** + * @brief Send a Search command + * + * Write a search to the server's search control point. + * + * @param conn Connection to the peer device + * @param search The search + * + * @return 0 if success, errno on failure. + */ +int bt_mcc_send_search(struct bt_conn *conn, struct mpl_search search); + +/** + * @brief Search Results Group Object ID + * + * @param conn Connection to the peer device + * + * @return 0 if success, errno on failure. + */ +int bt_mcc_read_search_results_obj_id(struct bt_conn *conn); +#endif /* CONFIG_BT_OTC */ + +/** + * @brief Read Content Control ID + * + * @param conn Connection to the peer device + * + * @return 0 if success, errno on failure. + */ +int bt_mcc_read_content_control_id(struct bt_conn *conn); + +#ifdef CONFIG_BT_OTC +/** + * @brief Read the current object metadata + * + * @param conn Connection to the peer device + * + * @return 0 if success, errno on failure. + */ +int bt_mcc_otc_read_object_metadata(struct bt_conn *conn); + +/** + * @brief Read the Icon Object + * + * @param conn Connection to the peer device + * + * @return 0 if success, errno on failure. + */ +int bt_mcc_otc_read_icon_object(struct bt_conn *conn); + +/** + * @brief Read the Track Segments Object + * + * @param conn Connection to the peer device + * + * @return 0 if success, errno on failure. + */ +int bt_mcc_otc_read_track_segments_object(struct bt_conn *conn); + +/** + * @brief Read the Current Track Object + * + * @param conn Connection to the peer device + * + * @return 0 if success, errno on failure. + */ +int bt_mcc_otc_read_current_track_object(struct bt_conn *conn); + +/** + * @brief Read the Next Track Object + * + * @param conn Connection to the peer device + * + * @return 0 if success, errno on failure. + */ +int bt_mcc_otc_read_next_track_object(struct bt_conn *conn); + +/** + * @brief Read the Current Group Object + * + * @param conn Connection to the peer device + * + * @return 0 if success, errno on failure. + */ +int bt_mcc_otc_read_current_group_object(struct bt_conn *conn); + +/** + * @brief Read the Parent Group Object + * + * @param conn Connection to the peer device + * + * @return 0 if success, errno on failure. + */ +int bt_mcc_otc_read_parent_group_object(struct bt_conn *conn); + +#if defined(CONFIG_BT_MCC_SHELL) +struct bt_otc_instance_t *bt_mcc_otc_inst(void); +#endif /* defined(CONFIG_BT_MCC_SHELL) */ +#endif /* CONFIG_BT_OTC */ + +#ifdef __cplusplus +} +#endif + +/** + * @} + */ + +#endif /* ZEPHYR_INCLUDE_BLUETOOTH_AUDIO_MCC__ */ diff --git a/subsys/bluetooth/audio/CMakeLists.txt b/subsys/bluetooth/audio/CMakeLists.txt index 8afebdc5f8a..0f705183413 100644 --- a/subsys/bluetooth/audio/CMakeLists.txt +++ b/subsys/bluetooth/audio/CMakeLists.txt @@ -33,3 +33,5 @@ if (CONFIG_BT_CSIS OR CONFIG_BT_CSIS_CLIENT) endif() zephyr_library_sources_ifdef(CONFIG_BT_OTC otc.c) + +zephyr_library_sources_ifdef(CONFIG_BT_MCC mcc.c) diff --git a/subsys/bluetooth/audio/Kconfig b/subsys/bluetooth/audio/Kconfig index a4be2b40677..62b0af4f1bd 100644 --- a/subsys/bluetooth/audio/Kconfig +++ b/subsys/bluetooth/audio/Kconfig @@ -63,5 +63,6 @@ rsource "Kconfig.vcs" rsource "Kconfig.mics" rsource "Kconfig.csis" rsource "Kconfig.otc" +rsource "Kconfig.mcs" endif # BT_AUDIO diff --git a/subsys/bluetooth/audio/Kconfig.mcs b/subsys/bluetooth/audio/Kconfig.mcs new file mode 100644 index 00000000000..44d9fbec9ac --- /dev/null +++ b/subsys/bluetooth/audio/Kconfig.mcs @@ -0,0 +1,207 @@ +# Bluetooth Audio - Media control configuration options + +# +# Copyright (c) 2020 Nordic Semiconductor +# +# SPDX-License-Identifier: Apache-2.0 +# + +if BT_AUDIO + +#### Media Control Service ################################ + +config BT_MCS + bool "Media Control Service Support" + select BT_CCID + select EXPERIMENTAL + help + This option enables support for the Media Control Service. + +if BT_MCS + +config BT_MCS_MEDIA_PLAYER_NAME + string "Media Player Name" + default "Player0" + help + Use this option to set the name of the media player. + +config BT_MCS_ICON_URL + string "Media player Icon URL" + default "http://server.some.where/path/icon.png" + help + Use this option to set the URL of the Media Player Icon. + +#### Debug logs ################################################################ + +config BT_DEBUG_MCS + bool "Media Control Service debug" + depends on BT_AUDIO_DEBUG + help + Use this option to enable Media Control Service debug logs for the + Bluetooth Audio functionality. + +endif # BT_MCS + +#### Media Control Client ################################ + +config BT_MCC + bool "Media Control Client Support" + select BT_GATT_CLIENT + select BT_GATT_AUTO_DISCOVER_CCC + select EXPERIMENTAL + help + This option enables support for the Media Control Client. + +if BT_MCC + +config BT_MCC_OTS + bool "Media Control Client support for Object Transfer Service" + depends on BT_OTC + help + Use this option to configure support in the Media Control Client for + an included Object Transfer Service in the Media Control Service. + +if BT_MCC_OTS + +config BT_MCC_OTC_OBJ_BUF_SIZE + int "The size of the buffer used for OTC object in MCC" + default 512 + range 1 65536 + help + Sets the size (in octets) of the buffer used for receiving the content + of objects. + Should be set large enough to fit any expected object. + +config BT_MCC_TOTAL_OBJ_CONTENT_MEM + int "Total memory size to use for storing the content of objects" + default 1 + range 0 65536 + help + Sets the total memory size (in octets) to use for storing the content + of objects. + This is used for the total memory pool buffer size from which memory + is allocated when reading object content. + +config BT_MCC_TRACK_SEGS_MAX_CNT + int "Maximum number of tracks segments in a track segment object" + default 25 + range 0 500 + help + Sets the maximum number of tracks segments in a track segment object. + If the received object is bigger, the remaining data in the object + will be ignored. + +config BT_MCC_GROUP_RECORDS_MAX + int "Maximum number of records in a group object" + default 25 + range 0 500 + help + Sets the maximum number of records in a group object. If the received + group object has more records than this, the remaining records in the + object will be ignored. + +endif # BT_MCC_OTS + +config BT_MCC_SHELL + bool "Media Control Client Shell Support" + default y if BT_SHELL + help + This option enables shell support for the Media Control Client. + +#### MCC Debug logs #### + +config BT_DEBUG_MCC + bool "Media Control Client debug" + depends on BT_AUDIO_DEBUG + help + Use this option to enable Media Control Client debug logs for the + Bluetooth Audio functionality. + +endif # BT_MCC + + +if BT_MCS || BT_MCC + +config BT_MCS_MEDIA_PLAYER_NAME_MAX + int "Max length of media player name" + default 20 + range 1 255 + help + Sets the maximum number of bytes (including the null termination) of + the name of the media player. + +config BT_MCS_ICON_URL_MAX + int "Max length of media player icon URL" + default 30 + range 1 255 + help + Sets the maximum number of bytes (including the null termination) of + the media player icon URL. + +config BT_MCS_TRACK_TITLE_MAX + int "Max length of the title of a track" + default 25 + range 1 255 + help + Sets the maximum number of bytes (including the null termination) of + the title of any track in the media player. + +config BT_MCS_GROUP_TITLE_MAX + int "Max length of the title of a group of tracks" + default BT_MCS_TRACK_TITLE_MAX + range 1 255 + help + Sets the maximum number of bytes (including the null termination) of + the title of any track in the media player. + +config BT_MCS_SEGMENT_NAME_MAX + int "Max length of the name of a track segment" + default 25 + range 1 255 + help + Sets the the maximum number of bytes (including the null termination) + of the name of any track segment in the media player. + +config BT_MCS_ICON_BITMAP_SIZE + int "Media player Icon bitmap object size" + default 127 + help + This option sets the maximum size (in octets) of the icon object. + +config BT_MCS_TRACK_SEG_MAX_SIZE + int "Maximum size for a track segment object" + default 127 + help + This option sets the maximum size (in octets) of a track segment object. + +config BT_MCS_TRACK_MAX_SIZE + int "Maximum size for a track object" + default 127 + help + This option sets the maximum size (in octets) of a track object. + +config BT_MCS_GROUP_MAX_SIZE + int "Maximum size for a group object" + default 127 + help + This option sets the maximum size (in octets) of a group object. + +config BT_MCS_MAX_OBJ_SIZE + int "Total memory size to use for storing the content of objects" + default 127 + range 0 65536 + help + Sets the total memory size (in octets) to use for storing the content of objects. + This is used for the total memory pool buffer size from which memory + is allocated when sending object content. + +config BT_DEBUG_MEDIA_PROXY + bool "Media Proxy debug" + depends on BT_AUDIO_DEBUG + help + Use this option to enable Media Proxy debug logs for the + Bluetooth Audio functionality. + +endif # BT_MCS || BT_MCC + +endif # BT_AUDIO diff --git a/subsys/bluetooth/audio/mcc.c b/subsys/bluetooth/audio/mcc.c new file mode 100644 index 00000000000..e85292a10f1 --- /dev/null +++ b/subsys/bluetooth/audio/mcc.c @@ -0,0 +1,2774 @@ +/** @file + * @brief Bluetooth Media Control Client/Protocol implementation + */ + +/* + * Copyright (c) 2019 - 2021 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "otc.h" +#include "otc_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", log_strdup(t)); \ + } \ + } while (0) + + +#define BT_DBG_ENABLED IS_ENABLED(CONFIG_BT_DEBUG_MCC) +#define LOG_MODULE_NAME bt_mcc +#include "common/log.h" + +#define FIRST_HANDLE 0x0001 +#define LAST_HANDLE 0xFFFF + +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_discover_params player_name_sub_disc_params; + struct bt_gatt_subscribe_params track_changed_sub_params; + struct bt_gatt_discover_params track_changed_sub_disc_params; + struct bt_gatt_subscribe_params track_title_sub_params; + struct bt_gatt_discover_params track_title_sub_disc_params; + struct bt_gatt_subscribe_params track_duration_sub_params; + struct bt_gatt_discover_params track_duration_sub_disc_params; + struct bt_gatt_subscribe_params track_position_sub_params; + struct bt_gatt_discover_params track_position_sub_disc_params; + struct bt_gatt_subscribe_params playback_speed_sub_params; + struct bt_gatt_discover_params playback_speed_sub_disc_params; + struct bt_gatt_subscribe_params seeking_speed_sub_params; + struct bt_gatt_discover_params seeking_speed_sub_disc_params; +#ifdef CONFIG_BT_MCC_OTS + struct bt_gatt_subscribe_params current_track_obj_sub_params; + struct bt_gatt_discover_params current_track_sub_disc_params; + struct bt_gatt_subscribe_params next_track_obj_sub_params; + struct bt_gatt_discover_params next_track_obj_sub_disc_params; + struct bt_gatt_subscribe_params parent_group_obj_sub_params; + struct bt_gatt_discover_params parent_group_obj_sub_disc_params; + struct bt_gatt_subscribe_params current_group_obj_sub_params; + struct bt_gatt_discover_params current_group_obj_sub_disc_params; +#endif /* CONFIG_BT_MCC_OTS */ + struct bt_gatt_subscribe_params playing_order_sub_params; + struct bt_gatt_discover_params playing_order_sub_disc_params; + struct bt_gatt_subscribe_params media_state_sub_params; + struct bt_gatt_discover_params media_state_sub_disc_params; + struct bt_gatt_subscribe_params cp_sub_params; + struct bt_gatt_discover_params cp_sub_disc_params; + struct bt_gatt_subscribe_params opcodes_supported_sub_params; + struct bt_gatt_discover_params opcodes_supported_sub_disc_params; +#ifdef CONFIG_BT_MCC_OTS + struct bt_gatt_subscribe_params scp_sub_params; + struct bt_gatt_discover_params scp_sub_disc_params; + struct bt_gatt_subscribe_params search_results_obj_sub_params; + struct bt_gatt_discover_params search_results_obj_sub_disc_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_otc_instance_t 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_otc_cb otc_cb; +#endif /* CONFIG_BT_MCC_OTS */ + + + +#ifdef CONFIG_BT_MCC_OTS +void on_obj_selected(struct bt_conn *conn, int err, + struct bt_otc_instance_t *otc_inst); + +void on_object_metadata(struct bt_conn *conn, int err, + struct bt_otc_instance_t *otc_inst, + uint8_t metadata_read); + +int on_icon_content(struct bt_conn *conn, uint32_t offset, uint32_t len, + uint8_t *data_p, + bool is_complete, struct bt_otc_instance_t *otc_inst); +#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_MCS_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; + } + + memcpy(&name, data, length); + name[length] = '\0'; + BT_DBG("Player name: %s", log_strdup(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_MCS_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"); + memcpy(&url, data, length); + url[length] = '\0'; + BT_DBG("Icon URL: %s", log_strdup(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_MCS_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; + } + memcpy(&title, data, length); + title[length] = '\0'; + BT_DBG("Track title: %s", log_strdup(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 { + memcpy(&cmd.opcode, params->data, sizeof(cmd.opcode)); + if (params->length == sizeof(cmd.opcode) + sizeof(cmd.param)) { + 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; + 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; + + 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; +} + +#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_otc_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_otc_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 (err %d)", + err); + if (mcc_cb && mcc_cb->discover_mcs) { + mcc_cb->discover_mcs(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; +} +#endif /* CONFIG_BT_MCC_OTS */ + + +/* This function is called when characteristics are found. + * The function will store handles, and optionally subscribe to, GMCS + * characteristics. + * After this, the function will start discovery of included services. + */ +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; + struct bt_gatt_subscribe_params *sub_params = NULL; + int err = 0; + + 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; + sub_params = &cur_mcs_inst->player_name_sub_params; + sub_params->disc_params = &cur_mcs_inst->player_name_sub_disc_params; +#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; + sub_params = &cur_mcs_inst->track_changed_sub_params; + sub_params->disc_params = &cur_mcs_inst->track_changed_sub_disc_params; + } 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; + sub_params = &cur_mcs_inst->track_title_sub_params; + sub_params->disc_params = &cur_mcs_inst->track_title_sub_disc_params; + } 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; + sub_params = &cur_mcs_inst->track_duration_sub_params; + sub_params->disc_params = &cur_mcs_inst->track_duration_sub_disc_params; + } 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; + sub_params = &cur_mcs_inst->track_position_sub_params; + sub_params->disc_params = &cur_mcs_inst->track_position_sub_disc_params; + } 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; + sub_params = &cur_mcs_inst->playback_speed_sub_params; + sub_params->disc_params = &cur_mcs_inst->playback_speed_sub_disc_params; + } 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; + sub_params = &cur_mcs_inst->seeking_speed_sub_params; + sub_params->disc_params = &cur_mcs_inst->seeking_speed_sub_disc_params; +#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; + sub_params = &cur_mcs_inst->current_track_obj_sub_params; + sub_params->disc_params = &cur_mcs_inst->current_track_sub_disc_params; + } 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; + sub_params = &cur_mcs_inst->next_track_obj_sub_params; + sub_params->disc_params = &cur_mcs_inst->next_track_obj_sub_disc_params; + } 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; + sub_params = &cur_mcs_inst->parent_group_obj_sub_params; + sub_params->disc_params = &cur_mcs_inst->parent_group_obj_sub_disc_params; + } 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; + sub_params = &cur_mcs_inst->current_group_obj_sub_params; + sub_params->disc_params = &cur_mcs_inst->current_group_obj_sub_disc_params; +#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; + sub_params = &cur_mcs_inst->playing_order_sub_params; + sub_params->disc_params = &cur_mcs_inst->playing_order_sub_disc_params; + } 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; + sub_params = &cur_mcs_inst->media_state_sub_params; + sub_params->disc_params = &cur_mcs_inst->media_state_sub_disc_params; + } 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; + sub_params = &cur_mcs_inst->cp_sub_params; + sub_params->disc_params = &cur_mcs_inst->cp_sub_disc_params; + } 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; + sub_params = &cur_mcs_inst->opcodes_supported_sub_params; + sub_params->disc_params = &cur_mcs_inst->opcodes_supported_sub_disc_params; +#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; + sub_params = &cur_mcs_inst->scp_sub_params; + sub_params->disc_params = &cur_mcs_inst->scp_sub_disc_params; + } 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; + sub_params = &cur_mcs_inst->search_results_obj_sub_params; + sub_params->disc_params = &cur_mcs_inst->search_results_obj_sub_disc_params; +#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; + } + + + if (subscribe_all && sub_params) { + BT_DBG("Subscribing - handle: 0x%04x", attr->handle); + + /* 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 = chrc->value_handle; + sub_params->notify = mcs_notify_handler; + bt_gatt_subscribe(conn, sub_params); + } + + /* Continue to search for more attributes */ + return BT_GATT_ITER_CONTINUE; + } + + /* No more attributes found */ + BT_DBG("Setup complete for GMCS"); + (void)memset(params, 0, sizeof(*params)); + +#ifdef CONFIG_BT_MCC_OTS + + /* Discover included services */ + 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("Discover of included service failed (err %d)", err); + if (mcc_cb && mcc_cb->discover_mcs) { + mcc_cb->discover_mcs(conn, err); + } + } + +#else + + /* If OTS is not configured, discovery ends here */ + if (mcc_cb && mcc_cb->discover_mcs) { + mcc_cb->discover_mcs(conn, err); + } + +#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 characeristics */ + 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("Discover failed (err %d)", err); + cur_mcs_inst = NULL; + if (mcc_cb && mcc_cb->discover_mcs) { + mcc_cb->discover_mcs(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.content_cb = on_icon_content; + otc_cb.obj_selected = on_obj_selected; + otc_cb.metadata_cb = on_object_metadata; + + BT_DBG("Current object selected callback: %p", otc_cb.obj_selected); + BT_DBG("Content callback: %p", otc_cb.content_cb); + BT_DBG("Metadata callback: %p", otc_cb.metadata_cb); +#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: Discover OTS service included in GMCS + * 4: Discover characteristics of OTS + */ +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)); + 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 = FIRST_HANDLE; + discover_params.end_handle = LAST_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; + } + + 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; + } + + 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; + } + + 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, 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; + } + + memcpy(cur_mcs_inst->write_buf, &cmd.opcode, length); + if (cmd.use_param) { + length += sizeof(cmd.param); + 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, 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; + } + + 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_conn *conn, int result, + struct bt_otc_instance_t *otc_inst) +{ + 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_conn *conn, uint32_t offset, uint32_t len, + uint8_t *data_p, bool is_complete, + struct bt_otc_instance_t *otc_inst) +{ + 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_OTC_CONTINUE; +} + +#if CONFIG_BT_DEBUG_MCC +struct track_seg_t { + uint8_t name_len; + char name[CONFIG_BT_MCS_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_MCS_SEGMENT_NAME_MAX) { + seg->name_len = + CONFIG_BT_MCS_SEGMENT_NAME_MAX - 1; + } + 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_conn *conn, uint32_t offset, + uint32_t len, uint8_t *data_p, bool is_complete, + struct bt_otc_instance_t *otc_inst) +{ + 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", + log_strdup(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_OTC_CONTINUE; +} + +int on_current_track_content(struct bt_conn *conn, uint32_t offset, + uint32_t len, uint8_t *data_p, bool is_complete, + struct bt_otc_instance_t *otc_inst) +{ + 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_OTC_CONTINUE; +} + +int on_next_track_content(struct bt_conn *conn, uint32_t offset, uint32_t len, + uint8_t *data_p, bool is_complete, + struct bt_otc_instance_t *otc_inst) +{ + 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_OTC_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_conn *conn, uint32_t offset, + uint32_t len, uint8_t *data_p, bool is_complete, + struct bt_otc_instance_t *otc_inst) +{ + 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, log_strdup(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_OTC_CONTINUE; +} + +int on_current_group_content(struct bt_conn *conn, uint32_t offset, + uint32_t len, uint8_t *data_p, bool is_complete, + struct bt_otc_instance_t *otc_inst) +{ + 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, log_strdup(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_OTC_CONTINUE; +} + +void on_object_metadata(struct bt_conn *conn, int err, + struct bt_otc_instance_t *otc_inst, + uint8_t metadata_read) +{ + BT_INFO("Object's meta data:"); + BT_INFO("\tCurrent size\t:%u", otc_inst->cur_object.current_size); + + if (otc_inst->cur_object.current_size > otc_obj_buf.size) { + BT_DBG("Object larger than allocated buffer"); + } + + bt_otc_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_otc_obj_metadata_read(conn, &cur_mcs_inst->otc, + BT_OTC_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->content_cb = on_icon_content; + + err = bt_otc_read(conn, &cur_mcs_inst->otc); + 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->content_cb = on_track_segments_content; + + err = bt_otc_read(conn, &cur_mcs_inst->otc); + 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->content_cb = on_current_track_content; + + err = bt_otc_read(conn, &cur_mcs_inst->otc); + 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->content_cb = on_next_track_content; + + err = bt_otc_read(conn, &cur_mcs_inst->otc); + 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->content_cb = on_parent_group_content; + + err = bt_otc_read(conn, &cur_mcs_inst->otc); + 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->content_cb = on_current_group_content; + + err = bt_otc_read(conn, &cur_mcs_inst->otc); + if (err) { + BT_DBG("Error reading the object: %d", err); + } + + return err; +} + +#if defined(CONFIG_BT_MCC_SHELL) +struct bt_otc_instance_t *bt_mcc_otc_inst(void) +{ + return &cur_mcs_inst->otc; +} +#endif /* defined(CONFIG_BT_MCC_SHELL) */ + +#endif /* CONFIG_BT_MCC_OTS */