BLE
BLE介绍
BLE(Bluetooh Low Energy)蓝牙低能耗技术是短距离、低成本、可互操作性的无线技术,本文档主要介绍ble相关的接口(使用),目前ble支持做主和做从,以及多连接,不支持BLE Mesh。
使能/关闭BLE功能
要关闭ble功能需要在menuconfig中关闭"BLUETOOTH"宏即可,打开时按需求打开其他相关功能宏,其设置如下图所示:
HCI功能
HCI(Host Interface Controller):是 Host 和 Controller 通信之间的接口。定义了特定的格式来控制蓝牙芯片来做相应的动作(比如:inquiry,connect,disconnect),HCI用于分析蓝牙问题。
HCI打开首先要在menuconfig中要打开配置,其次要通过finsh命令打开/关闭,发送finsh命令后需要重启设备。其中52X的HCI信息在HCPU打印,其他的在LCPU打印。
nvds update hci_log 1 //1为打开HCI,0为关闭HCI
要显示HCI时,对应的串口配置如下所示,才能正常显示HCI,HCI回放时,需要对应的.bin和.bintime文件。
HCI全显示:表示HCI数据会全部显示
HCI无数据:表示只显示HCI数据的头信息不显示具体数据
HCI隐藏:表示将HCI数据隐藏不显示(为了能更好的分析HCI数据在抓取HCI数据时请采用此种方式)
BLE连接
BLE无论第一次连接还是回连,连接的速度只和两个参数有关系,即设备的广播间隔和手机的扫描窗口/间隔比例:
设备的广播间隔:一般100-200ms的广播属于比较快的广播,只要手机的扫描间隔不是太离谱,都可以相对比较快的连上(以100ms为例,通常3秒以内,大部分情况1秒以内可以连上)。一般800-1000ms属于慢广播,这种更加省电,但是连接速度会更慢,连接速度和手机扫描间隔关系很大。
建议广播分为快慢两种广播,可以考虑采取下面两种方式进行切换:
开机或BLE断开后先快广播30秒到1分钟,到期后切换成慢广播。这样可以在拉距或异常断开后很快连接上。
开机、BLE断开、亮屏的时候都进入快广播,到时间后切换成慢广播。相比方法1实现方式会更复杂,但是能更快加速BLE的连接。
手机的扫描窗口/间隔比例:扫描窗口决定手机在一个扫描间隔进行扫描的时间,这个越接近扫描间隔的扫描速度越快。
IOS因为是系统自动扫描,而且扫描窗口间隔比例一般为1:1或1:2(30ms扫描窗口+30ms扫描间隔或30ms扫描窗口+60ms扫描间隔)
安卓/鸿蒙的扫描分成APK发起和系统发起:
APK调用BLE SCAN接口时候会有一个Flag来指定扫描的效率,最好是使用SCAN_MODE_LOW_LATENCY,这样手机通常会按照1:1或1:2的扫描窗口间隔比。
对于安卓/鸿蒙系统,在和手机配对并使能HID的情况下,部分手机会和IOS一样主动发起连接。
BLE回连
不同场景的回连方式会略有不同,通常来说BLE只能通过手机来连接设备端,而设备端只会打广播。
对于IOS系统,只要和IOS配对并且激活了ANCS,IOS会不停尝试回连设备端。所以即便APK没有发起BLE连接,设备端也可以看到BLE被连上。
有时候APK显示没有连接上BLE,可以借助IOS特点在手机设置或设备端观察BLE是否连接,来确认是APK本身问题还是BLE连接有异常。
如果设备端清除了本地LinkKey但是没有从iPhone的设置里面删除配对,会导致iPhone不停来连接设备但因为没有配对信息马上断开,这属于手机行为,可以在手机端进行和设备恢复出厂化设置时提示;也可以在设备恢复出厂化时不删除BLE配对信息,但这个需要在APK进行处理,避免BLE一直被不预期的iPhone连接。
对于安卓/鸿蒙系统,在和手机配对并使能HID的情况下,部分手机会和IOS一样主动发起连接,但有可能这个连接效率很低且会覆盖APK发起的连接,导致BLE连接会非常慢。除开这种情况,都需要APK主动发起连接,如果APK被杀掉,没有机会发起BLE连接,则手机和设备端蓝牙会一直连接不上。
BLE相关API
// 获取MTU大小
uint16_t get_mtu_size();
// 获取连接状态或广播状态
enum
{
CONNECT_STATUS,
ADV_STATUS
};
bool ble_get_ble_status(uint8_t type);
// 打开广播
void ble_start_advertising(void);
// 关闭广播
void ble_stop_advertising(void);
// 切换广播模式,不同的模式,广播信息不一样,用户可自行增加需要的模式
void ble_update_advertising(ble_roadcast_mode_t mode);
// 进入low power 函数
void ble_enter_low_power(uint8_t conn_idx);
// 退出low power 函数
void ble_exit_low_power(uint8_t conn_idx);
// 开关ble: 1, open; 0, close
void ble_switch_onoff(uint8_t switch_on)
// 向对端交互MTU,可在连接成功时调用,solution中没有调用,MTU需要手机主动设置。
uint8_t sibles_exchange_mtu(uint8_t conn_idx)
// 搜索其他ble设备
uint8_t ble_gap_scan_start(ble_gap_scan_start_t *scan_param)
// 停止搜索
uint8_t ble_gap_scan_stop(void)
// 创建连接
uint8_t ble_gap_create_connection(ble_gap_connection_create_param_t *conn_param)
// 更新连接参数
uint8_t ble_gap_update_conn_param(ble_gap_update_conn_param_t *conn_para)
// 删除所有绑定
uint8_t connection_manager_delete_all_bond(void)
// 获取指定连接索引上的地址值
uint8_t connection_manager_get_addr_by_conn_idx(uint8_t conn_idx, bd_addr_t *data)
// 断开连接
uint8_t ble_gap_disconnect(ble_gap_disconnect_t *conn)
BLE相应事件
ble相关的事件都可以添加到"ble_connect.c"文件的“ble_event_handler”函数中,这里对其主要事件进行说明。
// 连接成功事件
BLE_GAP_CONNECTED_IND
// 断连事件
BLE_GAP_DISCONNECTED_IND
// 连接参数事件,当前连接是什么参数
BLE_GAP_UPDATE_CONN_PARAM_IND
// MTU交互事件,当前MTU值
SIBLES_MTU_EXCHANGE_IND
// 搜索开始事件,可清除上一次搜索结果
BLE_GAP_SCAN_START_CNF
// 搜索结果事件
BLE_GAP_EXT_ADV_REPORT_IND
增加一个service uuid
客户增加自己的UUID时可参考"ble_app_msg.c"中的信息,"ble_app_msg.c"文件是与我们APP对应的UUID,客户如不使用时,可直接使用此文件进行修改。 其中如果客户增加增加了UUID,相应的初始化函数要在“notify_ble_start_adv”函数中有调用。 其中权限设置,对应如下代码所示,用户根据自己的需要设置对应的权限。
PERM(WRITE_REQ, ENABLE) | PERM(WRITE_COMMAND, ENABLE) | PERM(RD, ENABLE) | PERM(NTF, ENABLE) | PERM(IND, ENABLE), PERM(UUID_LEN, UUID_128) | PERM(RI, ENABLE)
/**
* 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
* +----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+
* |EXT | WS | I | N | WR | WC | RD | B | NP | IP | WP | RP |
* +----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+
*
* Bit [0-1] : Read Permission (0 = NO_AUTH, 1 = UNAUTH, 2 = AUTH, 3 = SEC_CON)
* Bit [2-3] : Write Permission (0 = NO_AUTH, 1 = UNAUTH, 2 = AUTH, 3 = SEC_CON)
* Bit [4-5] : Indication Permission (0 = NO_AUTH, 1 = UNAUTH, 2 = AUTH, 3 = SEC_CON)
* Bit [6-7] : Notification Permission (0 = NO_AUTH, 1 = UNAUTH, 2 = AUTH, 3 = SEC_CON)
*
* Bit [8] : Extended properties present (only relevant for a characteristic value)
* Bit [9] : Broadcast permission (only relevant for a characteristic value)
* Bit [10] : Write Command accepted
* Bit [11] : Write Signed accepted
* Bit [12] : Write Request accepted
* Bit [13] : Encryption key Size must be 16 bytes
*/
enum attm_perm_mask
{
/// retrieve all permission info
PERM_MASK_ALL = 0x0000,
/// Read Permission Mask
PERM_MASK_RP = 0x0003,
PERM_POS_RP = 0,
/// Write Permission Mask
PERM_MASK_WP = 0x000C,
PERM_POS_WP = 2,
/// Indication Access Mask
PERM_MASK_IP = 0x0030,
PERM_POS_IP = 4,
/// Notification Access Mask
PERM_MASK_NP = 0x00C0,
PERM_POS_NP = 6,
/// Broadcast descriptor present
PERM_MASK_BROADCAST = 0x0100,
PERM_POS_BROADCAST = 8,
/// Read Access Mask
PERM_MASK_RD = 0x0200,
PERM_POS_RD = 9,
/// Write Command Enabled attribute Mask
PERM_MASK_WRITE_COMMAND = 0x0400,
PERM_POS_WRITE_COMMAND = 10,
/// Write Request Enabled attribute Mask
PERM_MASK_WRITE_REQ = 0x0800,
PERM_POS_WRITE_REQ = 11,
/// Notification Access Mask
PERM_MASK_NTF = 0x1000,
PERM_POS_NTF = 12,
/// Indication Access Mask
PERM_MASK_IND = 0x2000,
PERM_POS_IND = 13,
/// Write Signed Enabled attribute Mask
PERM_MASK_WRITE_SIGNED = 0x4000,
PERM_POS_WRITE_SIGNED = 14,
/// Extended properties descriptor present
PERM_MASK_EXT = 0x8000,
PERM_POS_EXT = 15,
/// Properties
PERM_MASK_PROP = 0xFF00,
PERM_POS_PROP = 8,
};
初始化时Sercurity level for service如下代码所示,可根据实际需要进行设置
svc.sec_lvl = PERM(SVC_AUTH, SEC_CON) | PERM(SVC_UUID_LEN, UUID_128) | PERM(SVC_MI, ENABLE);
/**
* Service permissions
*
* 7 6 5 4 3 2 1 0
* +----+----+----+----+----+----+----+----+
* |SEC |UUID_LEN |DIS | AUTH |EKS | MI |
* +----+----+----+----+----+----+----+----+
*
* Bit [0] : Task that manage service is multi-instantiated (Connection index is conveyed)
* Bit [1] : Encryption key Size must be 16 bytes
* Bit [2-3]: Service Permission (0 = NO_AUTH, 1 = UNAUTH, 2 = AUTH, 3 = Secure Connect)
* Bit [4] : Disable the service
* Bit [5-6]: UUID Length (0 = 16 bits, 1 = 32 bits, 2 = 128 bits, 3 = RFU)
* Bit [7] : Secondary Service (0 = Primary Service, 1 = Secondary Service)
*/
enum attm_svc_perm_mask
{
/// Task that manage service is multi-instantiated
PERM_MASK_SVC_MI = 0x01,
PERM_POS_SVC_MI = 0,
/// Check Encryption key size for service Access
PERM_MASK_SVC_EKS = 0x02,
PERM_POS_SVC_EKS = 1,
/// Service Permission authentication
PERM_MASK_SVC_AUTH = 0x0C,
PERM_POS_SVC_AUTH = 2,
/// Disable the service
PERM_MASK_SVC_DIS = 0x10,
PERM_POS_SVC_DIS = 4,
/// Service UUID Length
PERM_MASK_SVC_UUID_LEN = 0x60,
PERM_POS_SVC_UUID_LEN = 5,
/// Service type Secondary
PERM_MASK_SVC_SECONDARY = 0x80,
PERM_POS_SVC_SECONDARY = 7,
};
BLE数据接收和发送
/**
* @brief Send new service attribute to remote using gatt notify.
* @param[in] conn_idx Connection index for the service.
* @param[in] value Attribute content buffer.
*/
int sibles_write_value(uint8_t conn_idx, sibles_value_t *value);
/**
* @brief Register GATT service callback.
* @param[in] hdl Service handle.
* @param[in] gcbk Service attribute get callback.
* @param[in] scbk Service attribute set callback.
*/
void sibles_register_cbk(sibles_hdl hdl, sibles_get_cbk gcbk, sibles_set_cbk scbk);
其中接收函数在初始化时设置的"scbk"函数中,可参考"ble_msg.c"文件中的对应函数。
BLE作为master连接其他设备
打开ble做主设备功能如下图所示:
可参考此参数"diss scan start 1 500 50 60000 -60"搜索设备
搜索得到的结果事件如下,其值没往UI传,需要自定义添加一个对应的事件。
连接设备,其中地址类型和地址在搜索结果中有。
其他操作参考如下命令
ble做主和多连接可参考“sdk\example\ble\central_and_peripheral\src\main.c"文件
BLE修改广播参数
如果要修改ble广播参数可在"ble_advertising_start"函数中更新"para"变化中的各个参数即可。
void ble_advertising_start(ble_roadcast_control_t control, ble_roadcast_mode_t mode)
BLE修改连接参数
我们发布的版本中,ble的速度会在“sibles_acquire_tx_pkts_hook”函数当数据包大于3(if (buffer_num >= BLE_SPEED_ADAPT_TX_COUNT))时更新为快速模式,连续30秒没数据传输时,自动更新为低速模式。OTA及表盘传输时速度也是自动控制的,不用客户控制。如客户想自行控制ble连接参考,可以参考下面的的对应函数实现。
//进入low power 命令,调此函数时ble进入低速模式
void ble_enter_low_power(uint8_t conn_idx);
//退出low power 命令,调此函数时ble进入快速模式
void ble_exit_low_power(uint8_t conn_idx);
/**
* @brief update conneciton parameters. 此函数可任意更新连接参数
* @param[conn_idx] connection index.
* @param[interval_level] interval level to be set, @see connection_manager_update_conneciton.
* @param[data] conneciton parameters vlaue, @see cm_conneciont_parameter_value_t.
* @return set status @see connection_manager_state
*/
uint8_t connection_manager_update_parameter(uint8_t conn_idx, uint8_t interval_level, uint8_t *data);
BLE修改MTU
在ble连接成功时可调用“sibles_exchange_mtu”函数交互MTU,或是对端设置MTU成功后“SIBLES_MTU_EXCHANGE_IND”事件上报设置的MTU值。
/**
* @brief Exchange MTU size.
* @param[in] conn_idx connection index.
* @retval result.
*/
uint8_t sibles_exchange_mtu(uint8_t conn_idx);
/**
* @brief User implmentation function. Stack will call this function to get max mtu.
* if not implmentation, stack will use default value as 1024.
* @param
* @return Maximum MTU value you want to use,
* should greater than or equal to 23, less than or equal to 1024.
*/
uint16_t ble_max_mtu_get();
BLE配对
ble在与手机进行配对时支持弹窗,只需打开“BLE_USING_PAIRING_CONFIRMATION”宏即可。
事件名称 |
描述 |
---|---|
BLE_APP_PAIRING_CONFIRMATION |
弹窗事件 |
BLE_APP_PAIRING_FAILED |
配对失败事件 |
BLE_APP_PAIRING_SUCCEED |
配对成功事件 |
//弹窗时用户点击“确定”或“取消”时需要调用的函数
uint8_t connection_manager_bond_ack_reply(uint8_t conn_idx, uint8_t command, bool accept);
BLE对接客户的BLE应用协议到solution
我们发布的ble通信协议中使用了nanopb协议进行编码和解码,客户如不需要可在menuconfig中关闭“PKG_USING_NANOPB”宏。
客户对接自己的协议时首先将"ble_msg.c"文件中的UUID修改为客户需要的UUID,然后在ble接收到数据后去调用我们提供的对应函数(如设置时间,设置自动心率等)可参考"protocol_setting.c"文件中的对应函数。
ANCS
ANCS(Apple Notification Center Service)是IOS系统中的一个通知服务,使用该服务的方式为,设备连接手机后,设备上的GATT客户端发现IOS手机端上的ANCS服务,并且使能其通知源Characteristics的notifys功能,之后IOS系统就会通过ble推送消息给设备。
ANCS消息
ANCS消息入口函数为“app_ancs_notification”最终调用“message_send_to_gui_thread”函数进行处理。
如需要增加ANCS的消息类型则在“app_ancs.c”文件中增加对应的类型即可。
static message_item_t message_item[] =
{
{"com.apple.MobileSMS", MESSAGE_TYPE_MESSAGE },
{"com.tencent.mqq", MESSAGE_TYPE_QQ },
{"com.tencent.xin", MESSAGE_TYPE_WECHAT },
{"net.whatsapp.WhatsApp", MESSAGE_TYPE_WHATSAPP },
{"com.facebook.Messenger", MESSAGE_TYPE_MESSENGER },
{"com.atebits.Tweetie2", MESSAGE_TYPE_TWITTER },
{"com.linkedin.Zephyr", MESSAGE_TYPE_LINKEDIN },
{"com.burbn.instagram", MESSAGE_TYPE_INSTAGRAM },
{"com.facebook.Facebook", MESSAGE_TYPE_FACEBOOK },
{"com.apple.mobilephone", MESSAGE_TYPE_INCOMINGCALL },
};
AMS
AMS全称Apple Media Service(苹果媒体服务),是提供给BLE设备的一种简单控制媒体应用程序的方式,并且用于获取已连接的 IOS 设备的媒体状态信息。 除了标准的通用属性配置文件(GATT)子程序集外,AMS没有任何依赖性。作为GATT客户端的设备在使用AMS时,可以自己访问和使用IOS设备提供的其他服务。
术语
Apple Media Service 称为 AMS AMS 服务的发布者(也就是我们的 ios 设备)应被称为媒体源(MS)。 AMS 服务的客户端(也就是我们的蓝牙设备)应被称为媒体控制器(MR)。 下面统一使用 媒体源 和 媒体控制 来描 IOS 设备和蓝牙设备。
AMS Service UUID
AMS 主服务(Service) UUID 为 89D3502B-0F36-433A-8EF4-C502AD55F8DC
AMS 服务下的三个特征(Characteristic) UUID:
远程命令(Remote Command)(writeable,notifiable)
9B3C81D8-57B1-4A8A-B8DF-0E56F7CA51C2
实体更新(Entity Update)(writeable with response,notifiable)
2F7CABCE-808D-411F-9A0C-BB92BA96C102
实体属性(Entity Attribute)(readable, writeable)
C6B2F38C-23AB-46D8-A6AB-A3A870BBD5D7
下图展示了媒体源和媒体控制器间的服务设置:
1.媒体控制器发现媒体源的 AMS 服务和特征
2.媒体控制器订阅媒体源实体更新
3.媒体控制器向媒体源写入需要监听的实体(EntityID),例如监听歌曲变化等
4.当媒体源监听的实体(EntityID)有变动时,会通知其变动内容
AMS Characteristic
实体
AMS 定义了 3 个不同的实体,每个实体都有一组不同的属性:
播放器 (Player): 当前活跃的媒体应用。该实体属性包括其名称、播放状态和播放音量等
队列 (Queue): 当前加载的播放队列。该实体的属性包括其队列数及其随机和重复模式等
音轨 (Track): 当前加载的音轨。该实体的属性包括其艺术家、标题和总的播放时间等。
播放器 (Player)属性表
// 获取播放器属性函数
app_ams_player_t *app_ams_get_player(void)
{
return app_ams_player;
}
通过播放器属性,我们可以获取 PlaverAttributelDName 当前音乐播放器的字符串,例如 IOS 手机使用的网易云音乐,那么媒体源推送的字符串转换为 unicode 编码后再转成中文,实际上就是“网易云音乐”,同理其他的音乐 APP,也会显示其相应的名称,非音乐APP 是不会有推送的。
PlayerAttributelDPlaybacklnfo 可以获取对应的播放状态、播放速率以及歌曲经过的时间,例如接收到推送为 49 44 49 46 48 44 55 49 46 49 52 55 表示 1,1.0,71.147,当前状态为播放、速率 1.0、已过时间 71.147 秒
PlaverAtributelDvolume 用于获取当前的音量浮点值,音量最大时为 1,最小时为 0,IOS 按一次音量按键,每次递进 0.0625。
注: 通过上面的信息,我们可以计算出当前经过的时间,公式如下:
CurrentElapsedTime = ElapsedTime + ((TimeNow-TimePlaybacklnfoWasReceived)* PlaybackRate)
上方展示的音乐进度条,也是依靠该方式和歌曲总长度计算的
队列 (Queue) 属性表
// 获取队列属性函数
app_ams_queue_t *app_ams_get_queue(void)
{
return app_ams_queue;
}
这一块为切换播放器播放模式时,会通知对应的模式变化。
音轨 (Track)属性表
// 获取音轨属性函数
app_ams_track_t *app_ams_get_track(void)
{
return app_ams_track;
}
这一块包含了歌曲名、歌手名、专辑名称以及当前音乐总时长。假设音乐时长03:08,那么媒体控制器接收到的则是188.000秒。
若媒体控制器订阅了Track属性,那么当媒体源播放音乐时,会将A音乐的曲名和歌手名推送给媒体控制器,每次有变动时,都会通知媒体控制器。
代码中对应事件为“BLE_APP_AMS_NOTI_IND”
/**
* @brief EntityID values.
*/
typedef enum
{
BLE_AMS_ENTITY_ID_PLAYER, /**< Player entity. The attribute is @ref ble_ams_entity_player_attribute_t. */
BLE_AMS_ENTITY_ID_QUEUE, /**< Queue entity. The attribute is @ref ble_ams_entity_queue_attribute_t. */
BLE_AMS_ENTITY_ID_TRACK, /**< Track entity. The attribute is @ref ble_ams_entity_queue_attribute_t. */
} ble_ams_entity_id_t;
/**
* @brief The structure of #BLE_AMS_ENTITY_ATTRIBUTE_PAIR_IND.
*/
typedef struct
{
uint8_t entity_id; /**< @see enum ble_ams_entity_id_t */
uint8_t attr_id; /**< Associated attribute ID for an dedicated entityID. */
uint8_t entity_up_flag; /**< Entity update flag. 1 is truncated. */
uint16_t len; /**< length of attribute value. */
uint8_t value[0]; /**< Attribute value. */
} ble_ams_entity_attr_value_t;
ble_ams_entity_attr_value_t *value = (ble_ams_entity_attr_value_t *)data;
ipc_send_msg_from_ble_to_app(BLE_APP_AMS_NOTI_IND, sizeof(ble_ams_entity_attr_value_t) + value->len, (uint8_t *)value);
//当收到“BLE_APP_AMS_NOTI_IND”事件时可依据 value->entity_id 值确定当前是什么属性发生了变化。
远程命令(Remote Command)
远程命令这个特征值是用于 媒体控制器 发送给 媒体源 的播放状态特征,例如播放/暂停、音量加/减、上/下首、循环播放等。
媒体控制器 发送命令格式如下:
媒体控制器 发现 媒体源 AMS 服务后,可以通过发送控制命令,播放/暂停、音量加减等操作媒体源播放器。
此特性还用于向 媒体控制器 报告当前 媒体源 支持的命令集,当媒体播放器支持的命令列表发生变化时,媒体源 会使用如下所示的格式,在该特征生产一条通知,通知中包括了支持的命令集:
通过该命令集,我们的蓝牙设备就可以对 IOS 设备的媒体播放器进行控制,该方式的音乐控制,相比较 HID,我觉得更优,因为 HID 控制只能在媒体播放器界面进行控制,模拟按键的方式去控制播放,并且 HID 的方式不能获取歌曲名、歌曲进度等,所以相较下,如果用于控制IOS 媒体播放器,那么便用 AMS 是最优的方式。
对应代码中命令及函数如下:
/**
* @brief Remote commandID values.
*/
typedef enum
{
BLE_AMS_CMD_PLAY, /**< Play command. */
BLE_AMS_CMD_PAUSE, /**< Pause command. */
BLE_AMS_CMD_TOGGLE_PLAY_PAUSE, /**< Toggle command. */
BLE_AMS_CMD_NEXT, /**< Next command. */
BLE_AMS_CMD_PREV, /**< Previous command. */
BLE_AMS_CMD_VOL_UP, /**< Voloume up command. */
BLE_AMS_CMD_VOL_DOWN, /**< Volume down command. */
BLE_AMS_CMD_REPEAT_MODE, /**< Repeat mode command. */
BLE_AMS_CMD_SHUFFLE_MODE, /**< Shuffle mode command. */
BLE_AMS_CMD_SKIP_FWD, /**< Skip forward command. */
BLE_AMS_CMD_SKIP_BACKWD, /**< Skip backward command. */
BLE_AMS_CMD_LIKE_TRACK, /**< Like track command. */
BLE_AMS_CMD_DISLIKE_TRACK, /**< Dislike track command. */
BLE_AMS_CMD_BOOKMARK_TRACK, /**< Bookmark track command. */
BLE_AMS_CMD_TOTAL /**< Total commands number. */
} ble_ams_cmd_t;
void app_ams_remote_cmd(ble_ams_cmd_t cmd);