wangzhibo
2026-09-11 659c470eba542383987aad227b7f918100f131fa
xiaozhi-esp32-2.2.6/main/boards/sening-xiaozhi-s3/mqtt.c
@@ -18,7 +18,7 @@
#include "app_task.h"
#define BOARD_MQTT_KEEPALIVE_SEC 120
#define BOARD_MQTT_STATE_REPORT_INTERVAL_MS 5000
#define BOARD_MQTT_STATE_REPORT_INTERVAL_MS 30000
#define BOARD_MQTT_RECONNECT_DELAY_MS 30000
#define BOARD_MQTT_NETWORK_TIMEOUT_MS 15000
#define BOARD_MQTT_FINAL_REPORT_WAIT_MS 10000
@@ -80,7 +80,6 @@
static void BoardMqtt_WaitConnectedForFinalReport(void);
static bool BoardMqtt_BeginTask(board_mqtt_task_type_t type);
static void BoardMqtt_FinishTask(board_mqtt_task_type_t type);
static bool BoardMqtt_PlaybackSpeedIsSupported(float speed);
static void BoardMqtt_StopPlaylistTask(void);
static esp_err_t BoardMqtt_StartPlaylistTask(void);
@@ -145,6 +144,7 @@
    int volume;
    bool has_volume;
    float speed;
    int repeat;
    int index;
    int count;
} board_mqtt_playlist_item_t;
@@ -616,7 +616,8 @@
        strcmp(action, "SET_CLOCK") == 0 || strcmp(action, "PLAY_MEDIA") == 0 ||
        strcmp(action, "STOP_MEDIA") == 0 || strcmp(action, "DELETE_FILE") == 0 ||
        strcmp(action, "SET_ALARM") == 0 || strcmp(action, "UNSET_ALARM") == 0 ||
        strcmp(action, "REBOOT") == 0;
        strcmp(action, "REBOOT") == 0 || strcmp(action, "START_APP_TASK") == 0 ||
        strcmp(action, "ENSURE_APP_DIRS") == 0;
}
static bool BoardMqtt_ActionIsTask(const char *action)
@@ -675,21 +676,6 @@
    esp_err_t err = DS2312_Set_Unix_Time_Saved((time_t)ts->valuedouble);
    return err == ESP_OK ? BoardMqtt_ResultOk("clock updated") :
        BoardMqtt_ResultFail(BOARD_MQTT_ERR_EXEC_FAILED, esp_err_to_name(err));
}
static bool BoardMqtt_PlaybackSpeedIsSupported(float speed)
{
    const float supported_speeds[] = {0.5f, 0.75f, 1.0f, 1.25f, 1.5f, 2.0f};
    for (size_t i = 0; i < sizeof(supported_speeds) / sizeof(supported_speeds[0]); ++i) {
        float diff = speed - supported_speeds[i];
        if (diff < 0) {
            diff = -diff;
        }
        if (diff < 0.001f) {
            return true;
        }
    }
    return false;
}
static char *BoardMqtt_ReadPlaylistFile(void)
@@ -850,13 +836,22 @@
                cJSON_Delete(root);
                return ESP_ERR_INVALID_ARG;
            }
            speed = (float)speed_json->valuedouble;
            speed = SDCard_Clamp_MP3_Speed((float)speed_json->valuedouble);
        }
        if (!BoardMqtt_PlaybackSpeedIsSupported(speed)) {
            cJSON_Delete(root);
            return ESP_ERR_INVALID_ARG;
        int repeat = 1;
        cJSON *repeat_json = cJSON_GetObjectItem(item, "repeat");
        if (!repeat_json) {
            repeat_json = cJSON_GetObjectItem(item, "count");
        }
        if (!repeat_json) {
            repeat_json = cJSON_GetObjectItem(item, "play_count");
        }
        if (repeat_json) {
            if (!BoardMqtt_NumberToInt(repeat_json, &repeat) || repeat < 1 || repeat > 99) {
                cJSON_Delete(root);
                return ESP_ERR_INVALID_ARG;
            }
        }
        cJSON *stored_item = cJSON_CreateObject();
        if (!stored_item) {
            cJSON_Delete(root);
@@ -868,6 +863,7 @@
            cJSON_AddNumberToObject(stored_item, "volume", volume_value);
        }
        cJSON_AddNumberToObject(stored_item, "speed", speed);
        cJSON_AddNumberToObject(stored_item, "repeat", repeat);
        cJSON_AddBoolToObject(stored_item, "completed", false);
        cJSON_AddItemToArray(playlist_items, stored_item);
    }
@@ -901,6 +897,7 @@
            cJSON *sd_path = cJSON_GetObjectItem(item, "sd_path");
            cJSON *volume = cJSON_GetObjectItem(item, "volume");
            cJSON *speed_json = cJSON_GetObjectItem(item, "speed");
            cJSON *repeat_json = cJSON_GetObjectItem(item, "repeat");
            if (!cJSON_IsString(path) || !cJSON_IsString(sd_path)) {
                cJSON_Delete(root);
                return false;
@@ -925,10 +922,14 @@
                }
                next_item->has_volume = true;
            }
            next_item->speed = cJSON_IsNumber(speed_json) ? (float)speed_json->valuedouble : 1.0f;
            if (!BoardMqtt_PlaybackSpeedIsSupported(next_item->speed)) {
                cJSON_Delete(root);
                return false;
            next_item->speed = SDCard_Clamp_MP3_Speed(
                cJSON_IsNumber(speed_json) ? (float)speed_json->valuedouble : 1.0f);
            next_item->repeat = 1;
            if (repeat_json) {
                int repeat = 1;
                if (BoardMqtt_NumberToInt(repeat_json, &repeat) && repeat >= 1) {
                    next_item->repeat = repeat > 99 ? 99 : repeat;
                }
            }
            next_item->index = index;
            next_item->count = count;
@@ -992,8 +993,10 @@
        BoardMqtt_ShowPlaylistAlert(alert_message, "music");
        SDCard_Show_Matching_JPG_For_MP3_Path(item.sd_path);
        ESP_LOGI(TAG, "playlist play item %d/%d: %s speed=%.2fx", item.index + 1, item.count, item.sd_path, item.speed);
        esp_err_t err = SDCard_Play_MP3_File_With_Speed(item.sd_path, item.speed);
        ESP_LOGI(TAG, "playlist play item %d/%d: %s speed=%.2fx repeat=%d",
                 item.index + 1, item.count, item.sd_path, item.speed, item.repeat);
        esp_err_t err = SDCard_Play_MP3_File_With_Speed_Repeat(
            item.sd_path, item.speed, item.repeat > 0 ? item.repeat : 1, false);
        if (err != ESP_OK) {
            ESP_LOGE(TAG, "playlist item playback failed: %s", esp_err_to_name(err));
            snprintf(alert_message, sizeof(alert_message), "播放失败 %d/%d: %s", item.index + 1, item.count, esp_err_to_name(err));
@@ -1894,6 +1897,38 @@
    return BoardMqtt_ResultOk("app task started");
}
static board_mqtt_action_result_t BoardMqtt_HandleEnsureAppDirs(cJSON *payload)
{
    if (!payload) {
        return BoardMqtt_ResultFail(BOARD_MQTT_ERR_INVALID_PARAM, "missing payload");
    }
    cJSON *app_id = cJSON_GetObjectItem(payload, "app_id");
    if (!cJSON_IsString(app_id) || !app_id->valuestring || app_id->valuestring[0] == '\0') {
        return BoardMqtt_ResultFail(BOARD_MQTT_ERR_INVALID_PARAM, "missing app_id");
    }
    esp_err_t err = Board_EnsureAppLayout(app_id->valuestring, NULL);
    if (err == ESP_ERR_INVALID_ARG || err == ESP_ERR_INVALID_SIZE) {
        return BoardMqtt_ResultFail(BOARD_MQTT_ERR_INVALID_PARAM, "invalid app_id");
    }
    if (err != ESP_OK) {
        return BoardMqtt_ResultFail(BOARD_MQTT_ERR_EXEC_FAILED, esp_err_to_name(err));
    }
    cJSON *tasks = cJSON_GetObjectItem(payload, "tasks");
    if (cJSON_IsArray(tasks)) {
        cJSON *task = NULL;
        cJSON_ArrayForEach(task, tasks) {
            if (!cJSON_IsString(task) || !task->valuestring) {
                return BoardMqtt_ResultFail(BOARD_MQTT_ERR_INVALID_PARAM, "invalid tasks");
            }
            err = Board_EnsureAppLayout(app_id->valuestring, task->valuestring);
            if (err != ESP_OK) {
                return BoardMqtt_ResultFail(BOARD_MQTT_ERR_EXEC_FAILED, esp_err_to_name(err));
            }
        }
    }
    return BoardMqtt_ResultOk("app dirs ensured");
}
static board_mqtt_action_result_t BoardMqtt_DispatchAction(const char *req_id, const char *action, cJSON *payload)
{
    if (strcmp(action, "SET_VOLUME") == 0) return BoardMqtt_HandleSetVolume(payload);
@@ -1909,6 +1944,7 @@
    if (strcmp(action, "DOWNLOAD") == 0) return BoardMqtt_HandleDownload(req_id, payload);
    if (strcmp(action, "OTA_UPDATE") == 0) return BoardMqtt_HandleOtaUpdate(req_id, payload);
    if (strcmp(action, "START_APP_TASK") == 0) return BoardMqtt_HandleStartAppTask(payload);
    if (strcmp(action, "ENSURE_APP_DIRS") == 0) return BoardMqtt_HandleEnsureAppDirs(payload);
    return BoardMqtt_ResultFail(BOARD_MQTT_ERR_UNSUPPORTED, "unsupported action");
}
@@ -2098,6 +2134,9 @@
    (void)arg;
    while (s_mqtt_client) {
        vTaskDelay(pdMS_TO_TICKS(BOARD_MQTT_STATE_REPORT_INTERVAL_MS));
        if (Board_Is_Audio_Busy()) {
            continue;
        }
        BoardMqtt_PublishState();
    }
    s_state_task_handle = NULL;