上一篇[ESP32-S31Functioncoreboard-1测评]重构环境并实现ESP-CLAW与蓝牙共存-电子产品世界论坛实现了esp-claw中加入蓝牙音频功能,现在采用Skill + Capability 组合模式,通过agent来控制蓝牙来实现对手机蓝牙的连接、断开、播放和暂停等操作。

框架为:
Skill 层(LLM 可见的“用户功能”) └── SKILL.md(bt_audio) │ metadata.cap_groups: ["cap_bt_audio"] ▼ Capability 层(LLM 可调用的“内部工具”) └── cap_bt_audio ├── bt_audio_play ├── bt_audio_pause ├── bt_audio_stop ├── bt_audio_next ├── bt_audio_prev ├── bt_audio_volume_set ├── bt_audio_volume_up ├── bt_audio_volume_down ├── bt_audio_connect └── bt_audio_disconnect │ ▼ 执行层(实际控制蓝牙) └── cmd_reg.c 的 bt_audio_* 函数 └── esp_bt_audio_* API
在edge_agent/components下新建组件bt_audio来实现蓝牙的功能,结构为:
bt_audio ├── include │ ├── cmd_reg.h │ ├── codec_defs.h │ ├── example_bt_audio.h │ ├── pool_reg.h │ └── stream_proc.h ├── src │ ├── cmd_reg.c │ ├── example_bt_audio.c │ ├── pool_reg.c │ └── stream_proc.c ├── CMakeLists.txt ├── idf_component.yml └── Kconfig
codec_defs.h、pool_reg.h、stream_proc.h、pool_reg.c与stream_proc.c保持原来一样,其他文件修改如下:
1,components/bt_audio/include/cmd_reg.h,去除cli的相关内容,不需要通过串口发送指令来控制蓝牙
/* * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #pragma once #include <stdint.h> #include <stdbool.h> #include "esp_err.h" /** * @brief 设备发现回调 * * 当蓝牙扫描到新设备时,由主事件回调调用。 * 在 A2DP Source 模式下,若设置了目标设备名,会尝试自动连接。 * * @param[in] name 设备名称 * @param[in] bda 设备蓝牙地址(6 字节) */ void bt_audio_on_device_found(const char *name, const uint8_t *bda); /** * @brief 连接状态变化回调 * * 用于维护当前连接设备的 BDA,便于 disconnect 等命令使用。 * * @param[in] bda 设备蓝牙地址 * @param[in] connected true=已连接,false=已断开 */ void bt_audio_on_conn_state_changed(const uint8_t *bda, bool connected); esp_err_t bt_audio_play(void); esp_err_t bt_audio_pause(void); esp_err_t bt_audio_stop(void); esp_err_t bt_audio_next(void); esp_err_t bt_audio_prev(void); esp_err_t bt_audio_volume_set(int volume); esp_err_t bt_audio_volume_up(void); esp_err_t bt_audio_volume_down(void); esp_err_t bt_audio_connect(const uint8_t bda[6]); esp_err_t bt_audio_disconnect(void);
2,components/bt_audio/include/example_bt_audio.h
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
void bt_audio_task(void);
#ifdef __cplusplus
}
#endif3,components/bt_audio/src/cmd_reg.c
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "esp_heap_caps.h"
#include "esp_system.h"
#include "esp_log.h"
#include "esp_bt_audio_defs.h"
#include "esp_bt_audio_classic.h"
#include "esp_bt_audio_vol.h"
#include "esp_bt_audio_media.h"
#include "esp_bt_audio_playback.h"
#include "esp_bt_audio_tel.h"
#include "esp_bt_audio_pb.h"
#include "esp_board_manager.h"
#include "esp_board_manager_defs.h"
#include "dev_audio_codec.h"
#include "cmd_reg.h"
/* 记录当前连接设备的 BDA,供 disconnect 等命令使用 */
static uint8_t target_device_bda[6] = {0};
#ifdef CONFIG_GMF_EXAMPLE_A2DP_SOURCE
/* A2DP Source 模式:记录目标设备名,用于发现后自动连接 */
static char target_device_name[32] = {0};
static const char *TAG = "CMD_REG";
#endif /* CONFIG_GMF_EXAMPLE_A2DP_SOURCE */
/**
* @brief 设备发现回调(Source 模式自动连接逻辑)
*/
void bt_audio_on_device_found(const char *name, const uint8_t *bda)
{
#if CONFIG_GMF_EXAMPLE_A2DP_SOURCE
if (strlen(target_device_name) > 0) {
if (strstr(name, target_device_name) != NULL) {
ESP_LOGI(TAG, "Found target device '%s', initiating auto-connect...", target_device_name);
esp_bt_audio_classic_discovery_stop();
esp_err_t ret = esp_bt_audio_classic_connect(ESP_BT_AUDIO_CLASSIC_ROLE_A2DP_SRC, (uint8_t *)bda);
if (ret == ESP_OK) {
ESP_LOGI(TAG, "Connecting to %s...n", name);
memset(target_device_name, 0, sizeof(target_device_name));
} else {
ESP_LOGE(TAG, "Failed to connect to %s: %sn", name, esp_err_to_name(ret));
}
}
}
#endif /* CONFIG_GMF_EXAMPLE_A2DP_SOURCE */
}
/**
* @brief 连接状态变化回调,维护 target_device_bda
*/
void bt_audio_on_conn_state_changed(const uint8_t *bda, bool connected)
{
if (connected) {
memcpy(target_device_bda, bda, sizeof(target_device_bda));
} else {
memset(target_device_bda, 0, sizeof(target_device_bda));
}
}
esp_err_t bt_audio_play(void)
{
return esp_bt_audio_playback_play();
}
esp_err_t bt_audio_pause(void)
{
return esp_bt_audio_playback_pause();
}
esp_err_t bt_audio_stop(void)
{
return esp_bt_audio_playback_stop();
}
esp_err_t bt_audio_next(void)
{
return esp_bt_audio_playback_next();
}
esp_err_t bt_audio_prev(void)
{
return esp_bt_audio_playback_prev();
}
esp_err_t bt_audio_volume_set(int volume)
{
if (volume < 0 || volume > 100) {
return ESP_ERR_INVALID_ARG;
}
#if CONFIG_GMF_EXAMPLE_A2DP_SOURCE
return esp_bt_audio_vol_set_absolute((uint32_t)volume);
#else
dev_audio_codec_handles_t *codec_handle = NULL;
esp_board_manager_get_device_handle(ESP_BOARD_DEVICE_NAME_AUDIO_DAC, (void **)&codec_handle);
esp_codec_dev_set_out_vol(codec_handle->codec_dev, volume);
return esp_bt_audio_vol_notify((uint32_t)volume);
#endif
}
esp_err_t bt_audio_volume_up(void)
{
#if CONFIG_GMF_EXAMPLE_A2DP_SOURCE
return esp_bt_audio_vol_set_relative(true);
#else
int current_volume = 0;
dev_audio_codec_handles_t *codec_handle = NULL;
esp_board_manager_get_device_handle(ESP_BOARD_DEVICE_NAME_AUDIO_DAC, (void **)&codec_handle);
esp_codec_dev_get_out_vol(codec_handle->codec_dev, ¤t_volume);
current_volume = (current_volume >= 90) ? 100 : current_volume + 10;
esp_codec_dev_set_out_vol(codec_handle->codec_dev, current_volume);
return esp_bt_audio_vol_notify(current_volume);
#endif
}
esp_err_t bt_audio_volume_down(void)
{
#if CONFIG_GMF_EXAMPLE_A2DP_SOURCE
return esp_bt_audio_vol_set_relative(false);
#else
int current_volume = 0;
dev_audio_codec_handles_t *codec_handle = NULL;
esp_board_manager_get_device_handle(ESP_BOARD_DEVICE_NAME_AUDIO_DAC, (void **)&codec_handle);
esp_codec_dev_get_out_vol(codec_handle->codec_dev, ¤t_volume);
current_volume = (current_volume <= 10) ? 0 : current_volume - 10;
esp_codec_dev_set_out_vol(codec_handle->codec_dev, current_volume);
return esp_bt_audio_vol_notify(current_volume);
#endif
}
esp_err_t bt_audio_connect(const uint8_t bda[6])
{
if (!bda) {
return ESP_ERR_INVALID_ARG;
}
#if CONFIG_GMF_EXAMPLE_A2DP_SOURCE
esp_err_t ret = esp_bt_audio_classic_connect(ESP_BT_AUDIO_CLASSIC_ROLE_A2DP_SRC, (uint8_t *)bda);
#else
esp_err_t ret = esp_bt_audio_classic_connect(ESP_BT_AUDIO_CLASSIC_ROLE_A2DP_SNK, (uint8_t *)bda);
#endif
if (ret == ESP_OK) {
memcpy(target_device_bda, bda, sizeof(target_device_bda));
}
return ret;
}
esp_err_t bt_audio_disconnect(void)
{
bool is_bda_empty = true;
for (int i = 0; i < sizeof(target_device_bda); ++i) {
if (target_device_bda[i] != 0) {
is_bda_empty = false;
break;
}
}
if (is_bda_empty) {
return ESP_ERR_INVALID_STATE;
}
#if CONFIG_GMF_EXAMPLE_A2DP_SOURCE
esp_err_t ret = esp_bt_audio_classic_disconnect(ESP_BT_AUDIO_CLASSIC_ROLE_A2DP_SRC, target_device_bda);
#else
esp_err_t ret = esp_bt_audio_classic_disconnect(ESP_BT_AUDIO_CLASSIC_ROLE_A2DP_SNK, target_device_bda);
#endif
if (ret == ESP_OK) {
memset(target_device_bda, 0, sizeof(target_device_bda));
}
return ret;
}4,components/bt_audio/src/example_bt_audio.c,主要去掉cli_init()。
5,components/bt_audio/CMakeLists.txt
idf_component_register( SRC_DIRS "src" INCLUDE_DIRS "include" REQUIRES "esp_bt_audio" "esp_board_manager" "gmf_core" "gmf_audio" "gmf_io" "gmf_misc" PRIV_REQUIRES "nvs_flash" "console" )
6,components/bt_audio/idf_component.yml
## IDF Component Manager Manifest File dependencies: espressif/esp_bt_audio: version: ^0.8 espressif/gmf_ai_audio: version: ^0.8 espressif/gmf_audio: version: ^0.8 espressif/gmf_core: version: ^0.8 espressif/gmf_io: version: ^0.8 espressif/gmf_misc: version: ^0.8
7,components/bt_audio/Kconfig
menu "BT Audio Basic Example (GMF)" menu "Classic Audio Roles Configuration" depends on BT_CLASSIC_ENABLED default GMF_EXAMPLE_A2DP_SINK choice GMF_EXAMPLE_A2DP_ROLE prompt "A2DP role" depends on BT_A2DP_ENABLE config GMF_EXAMPLE_A2DP_SINK bool "A2DP Sink" help Enable A2DP Sink role config GMF_EXAMPLE_A2DP_SOURCE bool "A2DP Source" help Enable A2DP Source role endchoice config GMF_EXAMPLE_HFP_HF depends on BT_HFP_CLIENT_ENABLE && GMF_EXAMPLE_A2DP_SINK bool "HFP Handsfree" default y help Enable HFP Handsfree role config GMF_EXAMPLE_AVRC_CT depends on BT_AVRCP_ENABLED bool "AVRC Controller" default y help Enable AVRC Controller role config GMF_EXAMPLE_AVRC_TG depends on BT_AVRCP_ENABLED bool "AVRC Target" default y help Enable AVRC Target role config GMF_EXAMPLE_PBAP_PCE depends on BT_PBAC_ENABLED && GMF_EXAMPLE_HFP_HF bool "Phone book client equipment" default n help Enable phone book client equipment role endmenu endmenu
8,main/CMakeLists.txt,添加组件bt_audio,才能使用蓝牙任务接口。如果报错:edge_agent_version.c:9:10: fatal error: esp_app_desc.h: No such file or directory,就是缺失组件esp_app_format。如果报错:edge_agent/main/main.c:25:10: fatal error: cmd_wifi.h: No such file or directory,就是缺失组件 cmd_wifi。
idf_component_register( SRC_DIRS "." INCLUDE_DIRS "." REQUIRES "bt_audio" "cmd_wifi" "esp_app_format" )
9,main/Kconfig.projbuild,去除components/bt_audio/Kconfig的内容,和原始文件一样即可
接下来在esp-claw/components/claw_capabilities下新增一个capability目录cap_bt_audio,结构为:
esp‑claw └── components └── claw_capabilities └── cap_bt_audio ├── include │ └── cap_bt_audio.h ├── skills │ └── SKILL.md ├── src │ └── cap_bt_audio.c └── CMakeLists.txt
具体代码实现:
10,components/claw_capabilities/cap_bt_audio/include/cap_bt_audio.h
#pragma once
#include "esp_err.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief 注册 BT Audio Capability Group
*
* 注册后在应用初始化时调用,Group 内的工具才能被框架识别。
* 此 Group 默认不应加入 LLM 可见白名单,需通过 Skill 激活后才可见。
*/
esp_err_t cap_bt_audio_register_group(void);
#ifdef __cplusplus
}
#endif11,components/claw_capabilities/cap_bt_audio/src/cap_bt_audio.c
#include "cap_bt_audio.h"
#include <stdio.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "esp_check.h"
#include "claw_cap.h"
#include "cmd_reg.h"
#include "cJSON.h"
static const char *TAG = "cap_bt_audio";
static esp_err_t bt_audio_play_execute(const char *input_json,
const claw_cap_call_context_t *ctx,
char *output, size_t output_size)
{
(void)input_json;
(void)ctx;
esp_err_t ret = bt_audio_play();
if (ret == ESP_OK) {
snprintf(output, output_size, "{"status":"ok"}");
} else {
snprintf(output, output_size, "Error: %s", esp_err_to_name(ret));
}
return ret;
}
static esp_err_t bt_audio_pause_execute(const char *input_json,
const claw_cap_call_context_t *ctx,
char *output, size_t output_size)
{
(void)input_json;
(void)ctx;
esp_err_t ret = bt_audio_pause();
if (ret == ESP_OK) {
snprintf(output, output_size, "{"status":"ok"}");
} else {
snprintf(output, output_size, "Error: %s", esp_err_to_name(ret));
}
return ret;
}
static esp_err_t bt_audio_stop_execute(const char *input_json,
const claw_cap_call_context_t *ctx,
char *output, size_t output_size)
{
(void)input_json;
(void)ctx;
esp_err_t ret = bt_audio_stop();
if (ret == ESP_OK) {
snprintf(output, output_size, "{"status":"ok"}");
} else {
snprintf(output, output_size, "Error: %s", esp_err_to_name(ret));
}
return ret;
}
static esp_err_t bt_audio_next_execute(const char *input_json,
const claw_cap_call_context_t *ctx,
char *output, size_t output_size)
{
(void)input_json;
(void)ctx;
esp_err_t ret = bt_audio_next();
if (ret == ESP_OK) {
snprintf(output, output_size, "{"status":"ok"}");
} else {
snprintf(output, output_size, "Error: %s", esp_err_to_name(ret));
}
return ret;
}
static esp_err_t bt_audio_prev_execute(const char *input_json,
const claw_cap_call_context_t *ctx,
char *output, size_t output_size)
{
(void)input_json;
(void)ctx;
esp_err_t ret = bt_audio_prev();
if (ret == ESP_OK) {
snprintf(output, output_size, "{"status":"ok"}");
} else {
snprintf(output, output_size, "Error: %s", esp_err_to_name(ret));
}
return ret;
}
static esp_err_t bt_audio_vol_set_execute(const char *input_json,
const claw_cap_call_context_t *ctx,
char *output, size_t output_size)
{
(void)ctx;
cJSON *json = cJSON_Parse(input_json);
if (!json) {
snprintf(output, output_size, "Error: invalid json");
return 0x102;
}
cJSON *vol = cJSON_GetObjectItem(json, "volume");
if (!cJSON_IsNumber(vol)) {
cJSON_Delete(json);
snprintf(output, output_size, "Error: missing 'volume'");
return 0x102;
}
int v = vol->valueint;
cJSON_Delete(json);
esp_err_t ret = bt_audio_volume_set(v);
if (ret == ESP_OK) {
snprintf(output, output_size, "{"status":"ok","volume":%d}", v);
} else {
snprintf(output, output_size, "Error: %s", esp_err_to_name(ret));
}
return ret;
}
static esp_err_t bt_audio_volume_up_execute(const char *input_json,
const claw_cap_call_context_t *ctx,
char *output, size_t output_size)
{
(void)input_json;
(void)ctx;
esp_err_t ret = bt_audio_volume_up();
if (ret == ESP_OK) {
snprintf(output, output_size, "{"status":"ok"}");
} else {
snprintf(output, output_size, "Error: %s", esp_err_to_name(ret));
}
return ret;
}
static esp_err_t bt_audio_vol_down_execute(const char *input_json,
const claw_cap_call_context_t *ctx,
char *output, size_t output_size)
{
(void)input_json;
(void)ctx;
esp_err_t ret = bt_audio_volume_down();
if (ret == ESP_OK) {
snprintf(output, output_size, "{"status":"ok"}");
} else {
snprintf(output, output_size, "Error: %s", esp_err_to_name(ret));
}
return ret;
}
static esp_err_t bt_audio_connect_execute(const char *input_json,
const claw_cap_call_context_t *ctx,
char *output, size_t output_size)
{
(void)ctx;
cJSON *json = cJSON_Parse(input_json);
if (!json) {
snprintf(output, output_size, "Error: invalid json");
return 0x102;
}
cJSON *mac = cJSON_GetObjectItem(json, "mac");
if (!cJSON_IsString(mac)) {
cJSON_Delete(json);
snprintf(output, output_size, "Error: missing 'mac'");
return 0x102;
}
uint8_t bda[6] = {0};
if (sscanf(mac->valuestring, "%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx",
&bda[0], &bda[1], &bda[2], &bda[3], &bda[4], &bda[5]) != 6) {
cJSON_Delete(json);
snprintf(output, output_size, "Error: invalid mac format");
return 0x102;
}
cJSON_Delete(json);
esp_err_t ret = bt_audio_connect(bda);
if (ret == ESP_OK) {
snprintf(output, output_size, "{"status":"connecting"}");
} else {
snprintf(output, output_size, "Error: %s", esp_err_to_name(ret));
}
return ret;
}
static esp_err_t bt_audio_disconnect_execute(const char *input_json,
const claw_cap_call_context_t *ctx,
char *output, size_t output_size)
{
(void)input_json;
(void)ctx;
esp_err_t ret = bt_audio_disconnect();
if (ret == ESP_OK) {
snprintf(output, output_size, "{"status":"disconnected"}");
} else {
snprintf(output, output_size, "Error: %s", esp_err_to_name(ret));
}
return ret;
}
/* ========== 描述符数组 ========== */
static const claw_cap_descriptor_t s_bt_audio_descriptors[] = {
{
.id = "bt_audio_play",
.name = "bt_audio_play",
.family = "bt_audio",
.description = "播放当前媒体",
.kind = CLAW_CAP_KIND_CALLABLE,
.cap_flags = CLAW_CAP_FLAG_CALLABLE_BY_LLM,
.execute = bt_audio_play_execute
},
{
.id = "bt_audio_pause",
.name = "bt_audio_pause",
.family = "bt_audio",
.description = "暂停播放",
.kind = CLAW_CAP_KIND_CALLABLE,
.cap_flags = CLAW_CAP_FLAG_CALLABLE_BY_LLM,
.execute = bt_audio_pause_execute
},
{
.id = "bt_audio_stop",
.name = "bt_audio_stop",
.family = "bt_audio",
.description = "停止播放",
.kind = CLAW_CAP_KIND_CALLABLE,
.cap_flags = CLAW_CAP_FLAG_CALLABLE_BY_LLM,
.execute = bt_audio_stop_execute
},
{
.id = "bt_audio_next",
.name = "bt_audio_next",
.family = "bt_audio",
.description = "切换下一首",
.kind = CLAW_CAP_KIND_CALLABLE,
.cap_flags = CLAW_CAP_FLAG_CALLABLE_BY_LLM,
.execute = bt_audio_next_execute
},
{
.id = "bt_audio_prev",
.name = "bt_audio_prev",
.family = "bt_audio",
.description = "切换上一首",
.kind = CLAW_CAP_KIND_CALLABLE,
.cap_flags = CLAW_CAP_FLAG_CALLABLE_BY_LLM,
.execute = bt_audio_prev_execute
},
{
.id = "bt_audio_vol_set",
.name = "bt_audio_vol_set",
.family = "bt_audio",
.description = "音量设置",
.kind = CLAW_CAP_KIND_CALLABLE,
.cap_flags = CLAW_CAP_FLAG_CALLABLE_BY_LLM,
.execute = bt_audio_vol_set_execute
},
{
.id = "bt_audio_vol_up",
.name = "bt_audio_vol_up",
.family = "bt_audio",
.description = "音量增加",
.kind = CLAW_CAP_KIND_CALLABLE,
.cap_flags = CLAW_CAP_FLAG_CALLABLE_BY_LLM,
.execute = bt_audio_volume_up_execute
},
{
.id = "bt_audio_vol_down",
.name = "bt_audio_vol_down",
.family = "bt_audio",
.description = "音量降低",
.kind = CLAW_CAP_KIND_CALLABLE,
.cap_flags = CLAW_CAP_FLAG_CALLABLE_BY_LLM,
.execute = bt_audio_vol_down_execute
},
{
.id = "bt_audio_connect",
.name = "bt_audio_connect",
.family = "bt_audio",
.description = "Connect to a Bluetooth device by MAC address. "
"The MAC must be in the form XX:XX:XX:XX:XX:XX.",
.kind = CLAW_CAP_KIND_CALLABLE,
.cap_flags = CLAW_CAP_FLAG_CALLABLE_BY_LLM,
.execute = bt_audio_connect_execute
},
{
.id = "bt_audio_disconnect",
.name = "bt_audio_disconnect",
.family = "bt_audio",
.description = "断开连接",
.kind = CLAW_CAP_KIND_CALLABLE,
.cap_flags = CLAW_CAP_FLAG_CALLABLE_BY_LLM,
.execute = bt_audio_disconnect_execute
},
};
/* ========== Group 定义与注册 ========== */
static const claw_cap_group_t s_bt_audio_group = {
.group_id = "cap_bt_audio",
.descriptors = s_bt_audio_descriptors,
.descriptor_count = sizeof(s_bt_audio_descriptors) / sizeof(s_bt_audio_descriptors[0]),
};
esp_err_t cap_bt_audio_register_group(void)
{
esp_err_t err = claw_cap_register_group(&s_bt_audio_group);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to register group: %s", esp_err_to_name(err));
} else {
ESP_LOGI(TAG, "Group 'cap_bt_audio' registered (%d tools)",
(int)s_bt_audio_group.descriptor_count);
}
return err;
}12,components/claw_capabilities/cap_bt_audio/CMakeLists.txt
idf_component_register( SRC_DIRS "src" INCLUDE_DIRS "include" REQUIRES "claw_cap" "bt_audio" PRIV_REQUIRES "freertos" )
13,components/claw_capabilities/cap_bt_audio/skills/SKILL.md
---
name: bt_audio
description: "蓝牙音频控制:播放、暂停、切歌、音量调节"
metadata:
manage_mode: runtime
cap_groups:
- cap_bt_audio
---
## 何时使用
当用户希望控制蓝牙音频播放时使用。
## 工具
- `bt_audio_play`:播放
- `bt_audio_pause`:暂停
- `bt_audio_stop`:停止
- `bt_audio_next`:下一首
- `bt_audio_prev`:上一首
- `bt_audio_vol_set`:设置音量,参数 `{"volume": 0-100}`
- `bt_audio_vol_up`:音量加 10
- `bt_audio_vol_down`:音量减 10
- `bt_audio_connect`:连接指定 MAC 的蓝牙设备,参数 `{"mac": "XX:XX:XX:XX:XX:XX"}`
- `bt_audio_disconnect`:断开当前连接的设备,无参数
## 注意
蓝牙任务在系统启动时已由 app_main 创建,无需额外开启。此外还需要把这个capability加入到app_claw中,才能正常使用cap_bt_audio
14,修改esp-claw/components/common/app_claw/Kconfig,在menu "App Claw Capabilities"中新增
config APP_CLAW_CAP_BT_AUDIO bool "Enable BT Audio capability" depends on BT_CLASSIC_ENABLED default y help Expose BT audio start/stop tools to the agent. Activate via the 'bt_audio' skill.
15,修改esp-claw/components/common/app_claw/app_capabilities.c
// 1,加入头文件
#if CONFIG_APP_CLAW_CAP_BT_AUDIO
#include "cap_bt_audio.h"
#endif
// 2,新增注册接口
#if CONFIG_APP_CLAW_CAP_BT_AUDIO
static esp_err_t app_cap_register_bt_audio(const app_claw_config_t *config,
const app_claw_storage_paths_t *paths)
{
(void)config;
(void)paths;
return cap_bt_audio_register_group();
}
#endif
// 3,在s_capability_group_entries[]中新增
#if CONFIG_APP_CLAW_CAP_BT_AUDIO
{ "cap_bt_audio", "BT Audio", "Register BT audio cap", true, NULL, app_cap_register_bt_audio },
#endif
// 4,在s_capability_group_infos[]中新增
#if CONFIG_APP_CLAW_CAP_BT_AUDIO
{ "cap_bt_audio", "BT Audio", true},
#endif16,修改esp-claw/components/common/app_claw/idf_component.yml,新增内容
cap_bt_audio:
rules:
- if: $CONFIG{APP_CLAW_CAP_BT_AUDIO} == True
path: ../../claw_capabilities/cap_bt_audio 之后在esp-claw Settings配置页面中可见功能已打开

注意:bt_audio_connect 只能连接已配对的设备。若手机从未与本设备配对过,需要先在手机的蓝牙设置中完成配对,之后才能通过 MAC 地址发起连接。
实现源码:esp-claw.zip
我要赚赏金
