From 220e4fab27b7f419b3e89e2f2d28ae789aac09d7 Mon Sep 17 00:00:00 2001
From: wangzhibo <wangzhibo@shsening.com>
Date: 星期四, 08 十月 2026 16:29:55 +0800
Subject: [PATCH] 5种场景模式家长端ok
---
xiaozhi-esp32-2.2.6/main/boards/sening-xiaozhi-s3/sening_xiaozhi_s3.cc | 166 ++++++++++++++++++++++++++++++++++++++++++++++++-------
1 files changed, 145 insertions(+), 21 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 7561ee8..a522ba8 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
@@ -25,6 +25,7 @@
#include <cmath>
#include <cstdio>
#include <cstring>
+#include <memory>
#include <string>
#include <esp_lcd_panel_io.h>
@@ -41,6 +42,7 @@
#include <ds3231.h>
#include "system.h"
#include "mqtt.h"
+#include "parent_schedule.h"
#define TAG "sening_xiaozhi_s3"
#define WIFI_RECONNECT_CONNECTED_BIT BIT0
@@ -119,6 +121,68 @@
{
DeviceState state = Application::GetInstance().GetDeviceState();
return state == kDeviceStateConnecting || state == kDeviceStateListening || state == kDeviceStateSpeaking;
+}
+
+extern "C" void Board_HoldIdleForServerPrompt(void)
+{
+ Application::GetInstance().Schedule([]() {
+ auto& app = Application::GetInstance();
+ app.HoldIdleForServerPrompt();
+ if (app.GetDeviceState() == kDeviceStateSpeaking) {
+ app.AbortSpeaking(kAbortReasonNone);
+ }
+ });
+}
+
+extern "C" void Board_ReleaseHoldIdle(void)
+{
+ Application::GetInstance().Schedule([]() {
+ Application::GetInstance().ReleaseHoldIdle();
+ });
+}
+
+extern "C" void Board_SetLessonDownloadBoost(bool enable)
+{
+ Board::GetInstance().SetPowerSaveLevel(
+ enable ? PowerSaveLevel::PERFORMANCE : PowerSaveLevel::LOW_POWER);
+}
+
+extern "C" void Board_AnnounceLessonSyncAndWait(void)
+{
+ Board_SetLessonDownloadBoost(true);
+ Application::GetInstance().Schedule([]() {
+ auto& app = Application::GetInstance();
+ app.HoldIdleForServerPrompt();
+ app.Alert("姝e湪鍚屾", "姝e湪鍚屾闊抽锛岃绋嶇瓑", "cloud_arrow_down");
+ if (!app.SendListenDetectNow("[lesson_sync]")) {
+ app.SendListenDetect("[lesson_sync]", true);
+ }
+ });
+
+ int waited_ms = 0;
+ bool saw_speaking = false;
+ while (waited_ms < 8000) {
+ DeviceState state = Application::GetInstance().GetDeviceState();
+ if (state == kDeviceStateSpeaking) {
+ saw_speaking = true;
+ } else if (saw_speaking && state == kDeviceStateIdle) {
+ break;
+ }
+ vTaskDelay(pdMS_TO_TICKS(100));
+ waited_ms += 100;
+ }
+ if (!saw_speaking) {
+ vTaskDelay(pdMS_TO_TICKS(2500));
+ }
+ Application::GetInstance().Schedule([]() {
+ auto& app = Application::GetInstance();
+ app.HoldIdleForServerPrompt();
+ app.GetAudioService().EnableVoiceProcessing(false);
+ app.GetAudioService().EnableWakeWordDetection(false);
+ if (app.GetDeviceState() == kDeviceStateListening) {
+ app.SetDeviceState(kDeviceStateIdle);
+ }
+ });
}
extern "C" bool Board_Is_Audio_Busy(void)
@@ -246,11 +310,21 @@
return ESP_FAIL;
}
+ constexpr size_t IO_BUF_SIZE = 16384;
+ char *io_buf = (char *)heap_caps_malloc(IO_BUF_SIZE, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
+ if (io_buf && setvbuf(fp, io_buf, _IOFBF, IO_BUF_SIZE) != 0) {
+ heap_caps_free(io_buf);
+ io_buf = nullptr;
+ }
+
auto network = Board::GetInstance().GetNetwork();
auto http = network->CreateHttp(0);
- http->SetTimeout(30000);
+ http->SetTimeout(120000);
if (!http->Open("GET", url)) {
fclose(fp);
+ if (io_buf) {
+ heap_caps_free(io_buf);
+ }
SetDownloadError(error_info, error_info_size, "open http failed");
return ESP_FAIL;
}
@@ -259,6 +333,9 @@
if (status != 200) {
fclose(fp);
http->Close();
+ if (io_buf) {
+ heap_caps_free(io_buf);
+ }
char message[64];
snprintf(message, sizeof(message), "http status %d", status);
SetDownloadError(error_info, error_info_size, message);
@@ -266,11 +343,27 @@
}
size_t content_length = http->GetBodyLength();
- constexpr size_t BUFFER_SIZE = 1024;
- char *buffer = (char *)heap_caps_malloc(BUFFER_SIZE, MALLOC_CAP_INTERNAL);
+ ESP_LOGI(TAG, "download start: %s content_length=%u", url, (unsigned)content_length);
+ constexpr size_t BUFFER_SIZE = 16384;
+ size_t buffer_size = BUFFER_SIZE;
+ char *buffer = (char *)heap_caps_malloc(buffer_size, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
+ if (!buffer) {
+ buffer_size = 4096;
+ buffer = (char *)heap_caps_malloc(buffer_size, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
+ }
+ if (!buffer) {
+ buffer = (char *)heap_caps_malloc(buffer_size, MALLOC_CAP_INTERNAL);
+ }
+ if (!buffer) {
+ buffer_size = 1024;
+ buffer = (char *)heap_caps_malloc(buffer_size, MALLOC_CAP_INTERNAL);
+ }
if (!buffer) {
fclose(fp);
http->Close();
+ if (io_buf) {
+ heap_caps_free(io_buf);
+ }
SetDownloadError(error_info, error_info_size, "no memory for download buffer");
return ESP_ERR_NO_MEM;
}
@@ -283,12 +376,14 @@
}
size_t total_read = 0;
+ size_t recent_read = 0;
int last_progress = -1;
int64_t last_report = 0;
+ int64_t last_speed_us = esp_timer_get_time();
bool ok = true;
bool canceled = false;
while (true) {
- int ret = http->Read(buffer, BUFFER_SIZE);
+ int ret = http->Read(buffer, buffer_size);
if (ret < 0) {
ESP_LOGE(TAG, "read HTTP failed: %d", ret);
SetDownloadError(error_info, error_info_size, "read http failed");
@@ -310,9 +405,16 @@
}
total_read += ret;
+ recent_read += (size_t)ret;
+ int64_t now = esp_timer_get_time();
+ if (now - last_speed_us >= 1000000) {
+ ESP_LOGI(TAG, "download %u/%u bytes %uB/s",
+ (unsigned)total_read, (unsigned)content_length, (unsigned)recent_read);
+ last_speed_us = now;
+ recent_read = 0;
+ }
if (progress_cb) {
int progress = content_length > 0 ? (int)(total_read * 100 / content_length) : 0;
- int64_t now = esp_timer_get_time();
bool report = content_length <= 0 || progress != last_progress || now - last_report >= 1000000 || progress >= 100;
if (report) {
last_progress = progress;
@@ -338,6 +440,9 @@
fclose(fp);
http->Close();
heap_caps_free(buffer);
+ if (io_buf) {
+ heap_caps_free(io_buf);
+ }
if (!ok) {
if (check_md5) {
@@ -776,8 +881,10 @@
task_id.c_str());
Board_SetShadowingListenRatio(
static_cast<float>(properties["listen_ratio"].value<double>()));
- sdcard_mp3_playlist_item_t playlist[SDCARD_MP3_PLAYLIST_MAX];
+ std::unique_ptr<sdcard_mp3_playlist_item_t, void(*)(void*)> playlist(
+ nullptr, heap_caps_free);
int playlist_n = 0;
+ int playlist_cap = 0;
if (!items_json.empty()) {
cJSON *root = cJSON_Parse(items_json.c_str());
cJSON *arr = root;
@@ -785,9 +892,25 @@
arr = cJSON_GetObjectItem(root, "items");
}
if (cJSON_IsArray(arr)) {
+ const int incoming = cJSON_GetArraySize(arr);
+ playlist_cap = incoming;
+ if (playlist_cap > SDCARD_MP3_PLAYLIST_MAX) {
+ ESP_LOGW(TAG, "playlist truncated incoming=%d max=%d",
+ incoming, SDCARD_MP3_PLAYLIST_MAX);
+ playlist_cap = SDCARD_MP3_PLAYLIST_MAX;
+ }
+ if (playlist_cap > 0) {
+ playlist.reset(static_cast<sdcard_mp3_playlist_item_t *>(
+ heap_caps_calloc(playlist_cap, sizeof(sdcard_mp3_playlist_item_t),
+ MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT)));
+ if (!playlist) {
+ cJSON_Delete(root);
+ return std::string("{\"accepted\":false,\"error\":\"no_mem\"}");
+ }
+ }
cJSON *it = nullptr;
cJSON_ArrayForEach(it, arr) {
- if (playlist_n >= SDCARD_MP3_PLAYLIST_MAX) {
+ if (playlist_n >= playlist_cap) {
break;
}
const cJSON *name_item = cJSON_GetObjectItem(it, "name");
@@ -800,22 +923,22 @@
continue;
}
if (!SDCard_Resolve_MP3_Path(name_item->valuestring,
- playlist[playlist_n].path,
- sizeof(playlist[playlist_n].path))) {
+ playlist.get()[playlist_n].path,
+ sizeof(playlist.get()[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;
+ playlist.get()[playlist_n].repeat = cJSON_IsNumber(rep) ? rep->valueint : 1;
+ if (playlist.get()[playlist_n].repeat < 1) {
+ playlist.get()[playlist_n].repeat = 1;
}
const cJSON *spd = cJSON_GetObjectItem(it, "speed");
- playlist[playlist_n].speed = cJSON_IsNumber(spd)
+ playlist.get()[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.get()[playlist_n].power_off_after = cJSON_IsTrue(po);
playlist_n++;
}
}
@@ -832,13 +955,13 @@
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;
+ name = playlist.get()[0].path;
+ speed = playlist.get()[0].speed;
+ repeat = playlist.get()[0].repeat;
+ power_off_after = playlist.get()[playlist_n - 1].power_off_after;
}
int64_t now = esp_timer_get_time();
- // 璺熻/澶嶈浼氳繛缁挱鍚屼竴鏂囦欢锛涗笉瑕佹寜璺緞鍘婚噸銆俧orce 缁欐棫鍥轰欢閲嶈瘯鐢ㄣ��
+ // 璺熻/娉涘惉浼氳繛缁挱鍚屼竴鏂囦欢锛涗笉瑕佹寜璺緞鍘婚噸銆俧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 &&
@@ -853,9 +976,9 @@
audio.EnableWakeWordDetection(false);
esp_err_t err = ESP_FAIL;
if (playlist_n > 0) {
- err = SDCard_Play_MP3_File_Playlist(playlist, playlist_n);
+ err = SDCard_Play_MP3_File_Playlist(playlist.get(), playlist_n);
if (err == ESP_OK) {
- SDCard_Show_Matching_JPG_For_MP3_Path(playlist[0].path);
+ SDCard_Show_Matching_JPG_For_MP3_Path(playlist.get()[0].path);
}
} else {
err = SDCard_Play_MP3_With_Speed_Repeat(name.data(), speed, repeat, power_off_after);
@@ -1108,6 +1231,7 @@
extern "C" esp_err_t Board_Enter_Power_Off(void)
{
+ Board_Schedule_OnEnteringPowerOff();
auto& board = static_cast<sening_xiaozhi_s3&>(Board::GetInstance());
return board.EnterPowerOffStandby();
}
--
Gitblit v1.9.1