wangzhibo
10 天以前 220e4fab27b7f419b3e89e2f2d28ae789aac09d7
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("正在同步", "正在同步音频,请稍等", "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();
                // 跟读/复读会连续播同一文件;不要按路径去重。force 给旧固件重试用。
                // 跟读/泛听会连续播同一文件;不要按路径去重。force 给旧固件重试用。
                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();
}