| | |
| | | #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 |
| | |
| | | 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); |
| | | |
| | |
| | | int volume; |
| | | bool has_volume; |
| | | float speed; |
| | | int repeat; |
| | | int index; |
| | | int count; |
| | | } board_mqtt_playlist_item_t; |
| | |
| | | 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) |
| | |
| | | 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) |
| | |
| | | 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); |
| | |
| | | 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); |
| | | } |
| | |
| | | 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; |
| | |
| | | } |
| | | 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; |
| | |
| | | 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)); |
| | |
| | | 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); |
| | |
| | | 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"); |
| | | } |
| | | |
| | |
| | | (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; |