From 659c470eba542383987aad227b7f918100f131fa Mon Sep 17 00:00:00 2001
From: wangzhibo <wangzhibo@shsening.com>
Date: 星期五, 11 九月 2026 15:09:05 +0800
Subject: [PATCH] 小智终端 5种 场景模式 OK
---
xiaozhi-esp32-2.2.6/main/boards/sening-xiaozhi-s3/sening_xiaozhi_s3.cc | 141 +++++++++++++++++++++++++++++++++++++++++++---
1 files changed, 131 insertions(+), 10 deletions(-)
diff --git a/xiaozhi-esp32-2.2.6/main/boards/sening-xiaozhi-s3/sening_xiaozhi_s3.cc b/xiaozhi-esp32-2.2.6/main/boards/sening-xiaozhi-s3/sening_xiaozhi_s3.cc
index 7d087c6..7561ee8 100644
--- a/xiaozhi-esp32-2.2.6/main/boards/sening-xiaozhi-s3/sening_xiaozhi_s3.cc
+++ b/xiaozhi-esp32-2.2.6/main/boards/sening-xiaozhi-s3/sening_xiaozhi_s3.cc
@@ -121,6 +121,15 @@
return state == kDeviceStateConnecting || state == kDeviceStateListening || state == kDeviceStateSpeaking;
}
+extern "C" bool Board_Is_Audio_Busy(void)
+{
+ if (Board_IsShadowingTask() || Board_IsRepeaterTask()) {
+ return true;
+ }
+ DeviceState state = Application::GetInstance().GetDeviceState();
+ return state == kDeviceStateConnecting || state == kDeviceStateSpeaking;
+}
+
extern "C" void Board_Show_JPG(const char *jpg_name)
{
if (!jpg_name || jpg_name[0] == '\0') {
@@ -719,31 +728,138 @@
return std::string(SDCard_Get_MP3_List());
});
+ mcp_server.AddTool("self.sd.ensure_app",
+ "Create SD directories for a subscribed app without flashing firmware. Unique app_id creates /{app_id}. Optional task_id also creates /{app_id}/{task_id}/lessons.",
+ PropertyList({
+ Property("app_id", kPropertyTypeString),
+ Property("task_id", kPropertyTypeString, std::string(""))
+ }),
+ [](const PropertyList& properties) -> ReturnValue {
+ std::string app_id = properties["app_id"].value<std::string>();
+ std::string task_id = properties["task_id"].value<std::string>();
+ esp_err_t err = Board_EnsureAppLayout(app_id.c_str(), task_id.c_str());
+ if (err != ESP_OK) {
+ return std::string("{\"ok\":false,\"error\":\"") + esp_err_to_name(err) + "\"}";
+ }
+ return std::string("{\"ok\":true,\"app_id\":\"") + app_id +
+ "\",\"task_id\":\"" + task_id + "\"}";
+ });
+
static std::string last_mp3_play_name;
static float last_mp3_play_speed = 1.0f;
static int64_t last_mp3_play_time = 0;
mcp_server.AddTool("self.mp3.play",
- "Play one MP3 file from the SD card. The name argument must exactly match a files[].name from self.mp3.get_list. Optional speed supports only 0.5, 0.75, 1.0, 1.25, 1.5, or 2.0; non-1.0 speeds change pitch and affect only SD card MP3 playback. Optional repeat is the number of full-file loops (default 1). Optional power_off_after requests existing power-off standby after a natural finish, not after stop. For play, random play, switch song, or named-song requests, call this tool before telling the user playback has started. Do not call this tool repeatedly for the same user request after it has returned accepted=true.",
+ "Play one MP3 file from the SD card. If name is omitted or empty, play the last lesson stored on device, or the first MP3 on the SD card. Otherwise name must exactly match a files[].name from self.mp3.get_list. Optional speed is a playback rate in [0.5, 2.0], default 1.0; values outside the range are clamped. Non-1.0 speeds change pitch and affect only SD card MP3 playback. Optional repeat is the number of full-file loops (default 1). Optional power_off_after requests existing power-off standby after a natural finish, not after stop. For play, random play, switch song, or named-song requests, call this tool before telling the user playback has started. Do not call this tool repeatedly for the same user request after it has returned accepted=true.",
PropertyList({
- Property("name", kPropertyTypeString),
+ Property("name", kPropertyTypeString, std::string("")),
+ Property("items", kPropertyTypeString, std::string("")),
Property("speed", kPropertyTypeNumber, 1.0),
Property("repeat", kPropertyTypeInteger, 1, 1, 99),
- Property("power_off_after", kPropertyTypeBoolean, false)
+ Property("power_off_after", kPropertyTypeBoolean, false),
+ Property("finish_task", kPropertyTypeBoolean, true),
+ Property("app_id", kPropertyTypeString, std::string("")),
+ Property("task_id", kPropertyTypeString, std::string("")),
+ Property("force", kPropertyTypeBoolean, false),
+ Property("listen_ratio", kPropertyTypeNumber, 1.2)
}),
[](const PropertyList& properties) -> ReturnValue {
std::string name = properties["name"].value<std::string>();
- float speed = static_cast<float>(properties["speed"].value<double>());
+ std::string items_json = properties["items"].value<std::string>();
+ float speed = SDCard_Clamp_MP3_Speed(static_cast<float>(properties["speed"].value<double>()));
int repeat = properties["repeat"].value<int>();
bool power_off_after = properties["power_off_after"].value<bool>();
- if (repeat < 1) {
- repeat = 1;
+ std::string task_id = properties["task_id"].value<std::string>();
+ bool force = properties["force"].value<bool>();
+ Board_SetFinishTaskAfterMp3(properties["finish_task"].value<bool>());
+ Board_SetActiveTask(
+ properties["app_id"].value<std::string>().c_str(),
+ task_id.c_str());
+ Board_SetShadowingListenRatio(
+ static_cast<float>(properties["listen_ratio"].value<double>()));
+ sdcard_mp3_playlist_item_t playlist[SDCARD_MP3_PLAYLIST_MAX];
+ int playlist_n = 0;
+ if (!items_json.empty()) {
+ cJSON *root = cJSON_Parse(items_json.c_str());
+ cJSON *arr = root;
+ if (root && cJSON_IsObject(root)) {
+ arr = cJSON_GetObjectItem(root, "items");
+ }
+ if (cJSON_IsArray(arr)) {
+ cJSON *it = nullptr;
+ cJSON_ArrayForEach(it, arr) {
+ if (playlist_n >= SDCARD_MP3_PLAYLIST_MAX) {
+ break;
+ }
+ const cJSON *name_item = cJSON_GetObjectItem(it, "name");
+ if (!cJSON_IsString(name_item) || !name_item->valuestring ||
+ name_item->valuestring[0] == '\0') {
+ name_item = cJSON_GetObjectItem(it, "url");
+ }
+ if (!cJSON_IsString(name_item) || !name_item->valuestring ||
+ name_item->valuestring[0] == '\0') {
+ continue;
+ }
+ if (!SDCard_Resolve_MP3_Path(name_item->valuestring,
+ playlist[playlist_n].path,
+ sizeof(playlist[playlist_n].path))) {
+ ESP_LOGW(TAG, "playlist skip missing %s", name_item->valuestring);
+ continue;
+ }
+ const cJSON *rep = cJSON_GetObjectItem(it, "repeat");
+ playlist[playlist_n].repeat = cJSON_IsNumber(rep) ? rep->valueint : 1;
+ if (playlist[playlist_n].repeat < 1) {
+ playlist[playlist_n].repeat = 1;
+ }
+ const cJSON *spd = cJSON_GetObjectItem(it, "speed");
+ playlist[playlist_n].speed = cJSON_IsNumber(spd)
+ ? SDCard_Clamp_MP3_Speed(static_cast<float>(spd->valuedouble))
+ : 1.0f;
+ const cJSON *po = cJSON_GetObjectItem(it, "power_off_after");
+ playlist[playlist_n].power_off_after = cJSON_IsTrue(po);
+ playlist_n++;
+ }
+ }
+ cJSON_Delete(root);
+ }
+ if (playlist_n < 1) {
+ char resolved[SDCARD_PATH_MAX_LEN] = {0};
+ strlcpy(resolved, name.c_str(), sizeof(resolved));
+ if (!Board_ResolveRepeaterPlayback(resolved, sizeof(resolved), &speed, &repeat, &power_off_after)) {
+ return std::string("{\"accepted\":false,\"error\":\"no_mp3\"}");
+ }
+ name = resolved;
+ if (repeat < 1) {
+ repeat = 1;
+ }
+ } else {
+ name = playlist[0].path;
+ speed = playlist[0].speed;
+ repeat = playlist[0].repeat;
+ power_off_after = playlist[playlist_n - 1].power_off_after;
}
int64_t now = esp_timer_get_time();
- if (name == last_mp3_play_name && std::fabs(speed - last_mp3_play_speed) < 0.001f && now - last_mp3_play_time < 15000000) {
+ // 璺熻/澶嶈浼氳繛缁挱鍚屼竴鏂囦欢锛涗笉瑕佹寜璺緞鍘婚噸銆俧orce 缁欐棫鍥轰欢閲嶈瘯鐢ㄣ��
+ bool lesson = (task_id == "shadowing" || task_id == "repeater");
+ if (!force && !lesson && playlist_n < 2 && name == last_mp3_play_name &&
+ std::fabs(speed - last_mp3_play_speed) < 0.001f &&
+ now - last_mp3_play_time < 400000) {
return std::string("{\"accepted\":true,\"duplicate_ignored\":true}");
}
-
- esp_err_t err = SDCard_Play_MP3_With_Speed_Repeat(name.data(), speed, repeat, power_off_after);
+ auto &audio = Application::GetInstance().GetAudioService();
+ if (task_id == "shadowing") {
+ audio.EnsureVoiceProcessorReady();
+ }
+ audio.EnableVoiceProcessing(false);
+ audio.EnableWakeWordDetection(false);
+ esp_err_t err = ESP_FAIL;
+ if (playlist_n > 0) {
+ err = SDCard_Play_MP3_File_Playlist(playlist, playlist_n);
+ if (err == ESP_OK) {
+ SDCard_Show_Matching_JPG_For_MP3_Path(playlist[0].path);
+ }
+ } else {
+ err = SDCard_Play_MP3_With_Speed_Repeat(name.data(), speed, repeat, power_off_after);
+ }
if (err != ESP_OK) {
return std::string("{\"accepted\":false,\"error\":\"") + esp_err_to_name(err) +
"\",\"retry\":\"use exact files[].name from self.mp3.get_list\"}";
@@ -751,7 +867,12 @@
last_mp3_play_name = name;
last_mp3_play_speed = speed;
last_mp3_play_time = now;
+ std::string remembered = name;
+ Application::GetInstance().Schedule([remembered, speed, repeat, power_off_after]() {
+ Board_RememberRepeaterPlayback(remembered.c_str(), speed, repeat, power_off_after);
+ });
return std::string("{\"accepted\":true,\"repeat\":") + std::to_string(repeat) +
+ ",\"items\":" + std::to_string(playlist_n > 0 ? playlist_n : 1) +
",\"power_off_after\":" + (power_off_after ? "true" : "false") + "}";
});
@@ -868,7 +989,7 @@
void OnDeviceReady() override {
xTaskCreate([](void *arg) {
(void)arg;
- vTaskDelay(pdMS_TO_TICKS(1500));
+ vTaskDelay(pdMS_TO_TICKS(2500));
Board_ResolveBootOrRestore(false);
vTaskDelete(nullptr);
}, "boot_app_task", 4096, nullptr, 3, nullptr);
--
Gitblit v1.9.1