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