T5 SMP(3.13.8)版本上设备端修改智能体相关配置demo

更新时间:2026-06-02 10:03:43LLM 副本以 Markdown 格式查看下载 PDF

1. 该demo仅供参考

2 在按键的回调里去做测试,这个是获取头像的demo:

STATIC VOID __on_ai_toy_audio_trigger_pin(UINT_T port, PUSH_KEY_TYPE_E type, INT_T cnt)
{
    TAL_PR_DEBUG("ai toy -> audio trigger pin pressed %d", type);

    /** Exit lowpower status when key press and device was in lowpower status */
    __on_ai_toy_key_press_exit_lowpower();

    if (LONG_KEY == type) {
        TAL_PR_INFO("[%s] swimming get bind role avatar", __func__);
        CHAR_T avatar_url[256] = {0};
        OPERATE_RET rt = swimming_get_bind_role_avatar(avatar_url, sizeof(avatar_url));
        if (OPRT_OK == rt && avatar_url[0] != '\0') 
		{
            TAL_PR_INFO("[%s] swimming role avatar url: %s", __func__, avatar_url);
            /* TODO: use avatar_url to download/display the role avatar image */
        }
        return;
    }
}

编写http 请求的接口

STATIC OPERATE_RET swimming_get_bind_role_avatar(CHAR_T *url_buf, INT_T buf_len)
{
    OPERATE_RET rt = OPRT_OK;
    ty_cJSON *result = NULL;
    CHAR_T post_content[] = "{}";

    TUYA_CHECK_NULL_RETURN(url_buf, OPRT_INVALID_PARM);

    rt = iot_httpc_common_post_simple("thing.ai.agent.role.get-bind-role", "1.0",
                                      post_content, NULL, &result);
    if (OPRT_OK != rt) {
        TAL_PR_ERR("swimming_get_bind_role_avatar request failed: %d", rt);
        return rt;
    }

    TUYA_CHECK_NULL_RETURN(result, OPRT_MID_HTTP_GET_RESP_ERROR);

    ty_cJSON *role_img = ty_cJSON_GetObjectItem(result, "roleImgUrl");
    if (role_img && role_img->valuestring) {
        snprintf(url_buf, buf_len, "%s", role_img->valuestring);
        TAL_PR_INFO("swimming_get_bind_role_avatar: %s", url_buf);
    } else {
        TAL_PR_WARN("swimming_get_bind_role_avatar: roleImgUrl not found");
        rt = OPRT_COM_ERROR;
    }

    ty_cJSON_Delete(result);
    return rt;
}

3 在按键的回调里去做测试,这个是修改智能体音色demo:

T5 SMP(3.13.8)版本上设备端修改智能体相关配置demo

按键触发

STATIC VOID __on_ai_toy_audio_trigger_pin(UINT_T port, PUSH_KEY_TYPE_E type, INT_T cnt)
{
    TAL_PR_DEBUG("ai toy -> audio trigger pin pressed %d", type);

    /** Exit lowpower status when key press and device was in lowpower status */
    __on_ai_toy_key_press_exit_lowpower();

    if (LONG_KEY == type) {
        CHAR_T lang_code[64] = {0};
        CHAR_T timbre_id[128] = {0};
        CHAR_T role_id[128] = {0};

        swimming_get_languages_list(lang_code, sizeof(lang_code));
        swimming_get_timbre_market_all_pages(lang_code, timbre_id, sizeof(timbre_id));
        swimming_get_custom_role_page(role_id, sizeof(role_id));
        swimming_update_custom_role(role_id, lang_code, timbre_id);
        return;
    }
}

接口封装

STATIC VOID swimming_get_languages_list(CHAR_T *out_lang_code, INT_T out_len)
{
    OPERATE_RET rt = OPRT_OK;
    ty_cJSON *result = NULL;

    TAL_PR_NOTICE("swimming: >>> get languages list");
    rt = iot_httpc_common_post_simple("thing.ai.agent.config.languages.list", "1.0", "{}", NULL, &result);
    if (OPRT_OK != rt || NULL == result) {
        TAL_PR_ERR("swimming: get languages list failed, rt=%d", rt);
        return;
    }

    if (!ty_cJSON_IsArray(result)) {
        TAL_PR_ERR("swimming: languages result is not array");
        ty_cJSON_Delete(result);
        return;
    }

    INT_T count = ty_cJSON_GetArraySize(result);
    TAL_PR_NOTICE("swimming: languages count=%d", count);

    INT_T i;
    for (i = 0; i < count; i++) {
        ty_cJSON *item = ty_cJSON_GetArrayItem(result, i);
        if (NULL == item) continue;

        ty_cJSON *lang_code = ty_cJSON_GetObjectItem(item, "langCode");
        ty_cJSON *lang_name = ty_cJSON_GetObjectItem(item, "langName");
        ty_cJSON *has_default = ty_cJSON_GetObjectItem(item, "hasDefault");

        TAL_PR_NOTICE("swimming: [%d] langCode=%s, langName=%s, hasDefault=%s",
                      i,
                      lang_code ? lang_code->valuestring : "null",
                      lang_name ? lang_name->valuestring : "null",
                      has_default ? (has_default->type == 1 ? "true" : "false") : "null");

        if (lang_code && lang_code->valuestring && out_lang_code) {
            if (0 == strcmp(lang_code->valuestring, "en")) {
                snprintf(out_lang_code, out_len, "%s", lang_code->valuestring);
            } else if (out_lang_code[0] == '\0') {
                snprintf(out_lang_code, out_len, "%s", lang_code->valuestring);
            }
        }
    }

    ty_cJSON_Delete(result);
    TAL_PR_NOTICE("swimming: <<< get languages list done, first langCode=%s", out_lang_code ? out_lang_code : "null");
}

STATIC VOID swimming_get_timbre_market_all_pages(CHAR_T *lang, CHAR_T *out_voice_id, INT_T out_len)
{
    OPERATE_RET rt = OPRT_OK;
    ty_cJSON *result = NULL;
    INT_T page_no = 1;
    INT_T page_size = 20;
    INT_T total_printed = 0;
    BOOL_T first_saved = FALSE;
    CHAR_T post_content[256] = {0};

    TAL_PR_NOTICE("swimming: >>> get timbre market all pages, lang=%s", lang ? lang : "null");

    while (1) {
        if (lang && lang[0] != '\0') {
            snprintf(post_content, sizeof(post_content),
                     "{\"pageNo\":%d,\"pageSize\":%d,\"lang\":\"%s\"}",
                     page_no, page_size, lang);
        } else {
            snprintf(post_content, sizeof(post_content),
                     "{\"pageNo\":%d,\"pageSize\":%d}",
                     page_no, page_size);
        }

		bk_printf("swimming content = %s \r\n",post_content);
		
        result = NULL;
        rt = iot_httpc_common_post_simple("thing.ai.timbre.market.page", "1.0", post_content, NULL, &result);
        if (OPRT_OK != rt || NULL == result) {
            TAL_PR_ERR("swimming: get timbre market page %d failed, rt=%d", page_no, rt);
            break;
        }

		bk_printf("11111111111111111 \r\n");

        CHAR_T *result_str = ty_cJSON_PrintUnformatted(result);
        if (result_str) {
            TAL_PR_NOTICE("swimming: timbre market page %d raw result: %s", page_no, result_str);
            ty_cJSON_FreeBuffer(result_str);
        }

        ty_cJSON *list = NULL;
        INT_T total_page = 0;

        if (ty_cJSON_IsArray(result)) {
            list = result;
        } else {
            list = ty_cJSON_GetObjectItem(result, "list");
            ty_cJSON *tp = ty_cJSON_GetObjectItem(result, "totalPage");
            if (tp) total_page = tp->valueint;
        }

        if (NULL == list || !ty_cJSON_IsArray(list)) {
            TAL_PR_ERR("swimming: timbre market page %d list is null or not array", page_no);
            ty_cJSON_Delete(result);
            break;
        }

        INT_T count = ty_cJSON_GetArraySize(list);
        TAL_PR_NOTICE("swimming: timbre market page %d, count=%d, totalPage=%d", page_no, count, total_page);

        if (count == 0) {
            ty_cJSON_Delete(result);
            break;
        }

        INT_T i;
        for (i = 0; i < count; i++) {
            ty_cJSON *item = ty_cJSON_GetArrayItem(list, i);
            if (NULL == item) continue;

            ty_cJSON *voice_id = ty_cJSON_GetObjectItem(item, "voiceId");
            ty_cJSON *voice_name = ty_cJSON_GetObjectItem(item, "voiceName");
            ty_cJSON *speed = ty_cJSON_GetObjectItem(item, "speed");
            ty_cJSON *tone = ty_cJSON_GetObjectItem(item, "tone");
            ty_cJSON *demo_url = ty_cJSON_GetObjectItem(item, "demoUrl");
            ty_cJSON *desc_tags = ty_cJSON_GetObjectItem(item, "descTags");
            ty_cJSON *support_langs = ty_cJSON_GetObjectItem(item, "supportLangs");

            TAL_PR_NOTICE("swimming: timbre[%d] voiceId=%s, voiceName=%s",
                          total_printed,
                          voice_id ? voice_id->valuestring : "null",
                          voice_name ? voice_name->valuestring : "null");
            TAL_PR_NOTICE("swimming: timbre[%d] speed=%s, tone=%s",
                          total_printed,
                          speed ? speed->valuestring : "null",
                          tone ? tone->valuestring : "null");
            TAL_PR_NOTICE("swimming: timbre[%d] demoUrl=%s",
                          total_printed,
                          demo_url ? demo_url->valuestring : "null");
            TAL_PR_NOTICE("swimming: timbre[%d] descTags count=%d, supportLangs count=%d",
                          total_printed,
                          desc_tags && ty_cJSON_IsArray(desc_tags) ? ty_cJSON_GetArraySize(desc_tags) : 0,
                          support_langs && ty_cJSON_IsArray(support_langs) ? ty_cJSON_GetArraySize(support_langs) : 0);

            if (!first_saved && voice_id && voice_id->valuestring && out_voice_id) {
                snprintf(out_voice_id, out_len, "%s", voice_id->valuestring);
                first_saved = TRUE;
            }
            total_printed++;
        }

        ty_cJSON_Delete(result);

        if (total_page > 0 && page_no >= total_page) break;
        if (count < page_size) break;

        page_no++;
    }

    TAL_PR_NOTICE("swimming: <<< get timbre market done, total=%d, first voiceId=%s",
                  total_printed, out_voice_id ? out_voice_id : "null");
}

STATIC VOID swimming_get_custom_role_page(CHAR_T *out_role_id, INT_T out_len)
{
    OPERATE_RET rt = OPRT_OK;
    ty_cJSON *result = NULL;
    INT_T page_no = 1;
    INT_T page_size = 20;
    BOOL_T first_saved = FALSE;
    CHAR_T post_content[128] = {0};

    TAL_PR_NOTICE("swimming: >>> get custom role page");

    while (1) {
        snprintf(post_content, sizeof(post_content), "{\"pageNo\":%d,\"pageSize\":%d}", page_no, page_size);

        result = NULL;
        rt = iot_httpc_common_post_simple("thing.ai.agent.role.custom-role.page", "1.0", post_content, NULL, &result);
        if (OPRT_OK != rt || NULL == result) {
            TAL_PR_ERR("swimming: get custom role page %d failed, rt=%d", page_no, rt);
            break;
        }

        ty_cJSON *list = NULL;
        INT_T total_page = 0;

        if (ty_cJSON_IsArray(result)) {
            list = result;
        } else {
            list = ty_cJSON_GetObjectItem(result, "list");
            ty_cJSON *tp = ty_cJSON_GetObjectItem(result, "totalPage");
            if (tp) total_page = tp->valueint;
        }

        if (NULL == list || !ty_cJSON_IsArray(list)) {
            TAL_PR_ERR("swimming: custom role page %d list is null or not array", page_no);
            ty_cJSON_Delete(result);
            break;
        }

        INT_T count = ty_cJSON_GetArraySize(list);
        TAL_PR_NOTICE("swimming: custom role page %d, count=%d, totalPage=%d", page_no, count, total_page);

        if (count == 0) {
            ty_cJSON_Delete(result);
            break;
        }

        INT_T i;
        for (i = 0; i < count; i++) {
            ty_cJSON *item = ty_cJSON_GetArrayItem(list, i);
            if (NULL == item) continue;

            ty_cJSON *role_id = ty_cJSON_GetObjectItem(item, "roleId");
            ty_cJSON *role_name = ty_cJSON_GetObjectItem(item, "roleName");
            ty_cJSON *use_timbre_id = ty_cJSON_GetObjectItem(item, "useTimbreId");
            ty_cJSON *use_timbre_name = ty_cJSON_GetObjectItem(item, "useTimbreName");
            ty_cJSON *use_lang_code = ty_cJSON_GetObjectItem(item, "useLangCode");
            ty_cJSON *use_lang_name = ty_cJSON_GetObjectItem(item, "useLangName");
            ty_cJSON *use_llm_name = ty_cJSON_GetObjectItem(item, "useLlmName");
            ty_cJSON *template_id = ty_cJSON_GetObjectItem(item, "templateId");

            TAL_PR_NOTICE("swimming: role[%d] roleId=%s, roleName=%s",
                          i,
                          role_id ? role_id->valuestring : "null",
                          role_name ? role_name->valuestring : "null");
            TAL_PR_NOTICE("swimming: role[%d] useTimbreId=%s, useTimbreName=%s",
                          i,
                          use_timbre_id ? use_timbre_id->valuestring : "null",
                          use_timbre_name ? use_timbre_name->valuestring : "null");
            TAL_PR_NOTICE("swimming: role[%d] useLangCode=%s, useLangName=%s",
                          i,
                          use_lang_code ? use_lang_code->valuestring : "null",
                          use_lang_name ? use_lang_name->valuestring : "null");
            TAL_PR_NOTICE("swimming: role[%d] useLlmName=%s, templateId=%s",
                          i,
                          use_llm_name ? use_llm_name->valuestring : "null",
                          template_id ? template_id->valuestring : "null");

            if (!first_saved && role_id && role_id->valuestring && out_role_id) {
                snprintf(out_role_id, out_len, "%s", role_id->valuestring);
                first_saved = TRUE;
            }
        }

        ty_cJSON_Delete(result);

        if (total_page > 0 && page_no >= total_page) break;
        if (count < page_size) break;

        page_no++;
    }

    TAL_PR_NOTICE("swimming: <<< get custom role done, first roleId=%s", out_role_id ? out_role_id : "null");
}

STATIC VOID swimming_update_custom_role(CHAR_T *role_id, CHAR_T *lang_code, CHAR_T *timbre_id)
{
    OPERATE_RET rt = OPRT_OK;
    ty_cJSON *result = NULL;
    CHAR_T post_content[512] = {0};

    if (NULL == role_id || role_id[0] == '\0') {
        TAL_PR_ERR("swimming: update custom role failed, roleId is empty");
        return;
    }

    TAL_PR_NOTICE("swimming: >>> update custom role, roleId=%s, langCode=%s, timbreId=%s",
                  role_id, lang_code ? lang_code : "null", timbre_id ? timbre_id : "null");

    snprintf(post_content, sizeof(post_content),
             "{\"roleId\":\"%s\",\"useLangCode\":\"%s\",\"useTimbreId\":\"%s\"}",
             role_id,
             lang_code ? lang_code : "",
             timbre_id ? timbre_id : "");

    rt = iot_httpc_common_post_simple("thing.ai.agent.role.custom-role.update", "1.0", post_content, NULL, &result);
    if (OPRT_OK != rt) {
        TAL_PR_ERR("swimming: update custom role failed, rt=%d", rt);
        if (result) ty_cJSON_Delete(result);
        return;
    }

    TAL_PR_NOTICE("swimming: <<< update custom role success");
    if (result) ty_cJSON_Delete(result);
}

4 在按键的回调里去做测试,这个是修改模版角色里的语种的demo

设备侧交互流程比如:
1、进入角色列表:设备侧一次性查询模板角色列表(thing.ai.agent.role.role-template.list),查询所有的自定义角色列表(thing.ai.agent.role.custom-role.page)
2、如果某个模板角色存在已经创建好的自定义角色则隐藏;否则显示。对应屏幕上会有些许自定义角色和些许模板角色
3、点击角色修改语言逻辑:

  • 3.0. 点击角色弹出语种选择(查语种thing.ai.agent.config.languages.list)
  • 3.1. 查询支持该语种的音色列表(thing.ai.timbre.market.page)
  • 3.2. 如果点的是自定义角色则直接修改(thing.ai.agent.role.custom-role.update);
  • 3.3. 如果点的是模板角色,调用根据模板角色创建自定义角色接口把语种带进去(thing.ai.agent.role.custom-role.create-from-template);
  • 3.4.刷新界面,重复1步骤

切语种的话还要注意如果音色不支持切的语种还要切音色

按键触发

STATIC VOID __on_ai_toy_audio_trigger_pin(UINT_T port, PUSH_KEY_TYPE_E type, INT_T cnt)
{
    TAL_PR_DEBUG("ai toy -> audio trigger pin pressed %d", type);
    
    /** Exit lowpower status when key press and device was in lowpower status */
    __on_ai_toy_key_press_exit_lowpower();

    if (LONG_KEY == type)
   {
	tal_workq_schedule(WORKQ_SYSTEM, swimming_role_config_task, NULL);
        return;	
    }
	
    if (SEQ_KEY == type) {

        TAL_PR_DEBUG("[%s] trigger mode:%d, device mode:%d", __func__, s_ai_toy->cfg.trigger_mode, s_ai_toy->cfg.device_mode);
        if (s_ai_toy->cfg.device_mode != AI_DEVICE_MODE_CHAT) {
            TAL_PR_INFO("[%s] current device mode %d ====> %d", __func__, s_ai_toy->cfg.device_mode, AI_DEVICE_MODE_CHAT);
            wukong_audio_player_stop(AI_PLAYER_ALL);
            wukong_ai_agent_chat_break(NULL);
            wukong_ai_device_mode_switch(AI_DEVICE_MODE_CHAT);
            return;
        }

        /* Double press: stop all playback, send chat break, cycle trigger mode, play mode alert.
         * sub_mode_cycle -> tuya_ai_toy_trigger_mode_set persists the new sub-mode. */
        wukong_audio_player_stop(AI_PLAYER_ALL);
        wukong_ai_agent_chat_break(NULL);
        wukong_ai_chat_sub_mode_cycle();
        wukong_audio_player_alert(AI_TOY_ALERT_TYPE_LONG_KEY_TALK + s_ai_toy->cfg.trigger_mode, TRUE);
        return;
    }

    /* Single/long press: pass to wukong key handler (e.g. hold to talk). */
    wukong_ai_mode_dispatch(AI_MODE_OP_KEY, &type, 0);
}

接口封装

STATIC VOID swimming_get_languages_list(CHAR_T *out_lang_code, INT_T out_len)
{
    OPERATE_RET rt = OPRT_OK;
    ty_cJSON *result = NULL;

    TAL_PR_NOTICE("swimming: >>> get languages list");
    rt = iot_httpc_common_post_simple("thing.ai.agent.config.languages.list", "1.0", "{}", NULL, &result);
    if (OPRT_OK != rt || NULL == result) {
        TAL_PR_ERR("swimming: get languages list failed, rt=%d", rt);
        return;
    }

    if (!ty_cJSON_IsArray(result)) {
        TAL_PR_ERR("swimming: languages result is not array");
        ty_cJSON_Delete(result);
        return;
    }

    INT_T count = ty_cJSON_GetArraySize(result);
    TAL_PR_NOTICE("swimming: languages count=%d", count);

    INT_T i;
    for (i = 0; i < count; i++) {
        ty_cJSON *item = ty_cJSON_GetArrayItem(result, i);
        if (NULL == item) continue;

        ty_cJSON *lang_code = ty_cJSON_GetObjectItem(item, "langCode");
        ty_cJSON *lang_name = ty_cJSON_GetObjectItem(item, "langName");
        ty_cJSON *has_default = ty_cJSON_GetObjectItem(item, "hasDefault");

        TAL_PR_NOTICE("swimming: [%d] langCode=%s, langName=%s, hasDefault=%s",
                      i,
                      lang_code ? lang_code->valuestring : "null",
                      lang_name ? lang_name->valuestring : "null",
                      has_default ? (has_default->type == 1 ? "true" : "false") : "null");

        if (lang_code && lang_code->valuestring && out_lang_code) {
            if (0 == strcmp(lang_code->valuestring, "en")) {
                snprintf(out_lang_code, out_len, "%s", lang_code->valuestring);
            } else if (out_lang_code[0] == '\0') {
                snprintf(out_lang_code, out_len, "%s", lang_code->valuestring);
            }
        }
    }

    ty_cJSON_Delete(result);
    TAL_PR_NOTICE("swimming: <<< get languages list done, first langCode=%s", out_lang_code ? out_lang_code : "null");
}

STATIC ty_cJSON *swimming_get_role_template_list(VOID)
{
    OPERATE_RET rt = OPRT_OK;
    ty_cJSON *result = NULL;
    CHAR_T post_content[128] = {0};

    TAL_PR_NOTICE("swimming: >>> get role template list");

    snprintf(post_content, sizeof(post_content), "{}");

    rt = iot_httpc_common_post_simple("thing.ai.agent.role.role-template.list", "1.0", post_content, NULL, &result);
    if (OPRT_OK != rt || NULL == result) {
        TAL_PR_ERR("swimming: get role template list failed, rt=%d", rt);
        if (result) ty_cJSON_Delete(result);
        return NULL;
    }

    if (!ty_cJSON_IsArray(result)) {
        CHAR_T *result_str = ty_cJSON_PrintUnformatted(result);
        if (result_str) {
            TAL_PR_NOTICE("swimming: role template list raw: %s", result_str);
            ty_cJSON_FreeBuffer(result_str);
        }
        ty_cJSON_Delete(result);
        return NULL;
    }

    INT_T count = ty_cJSON_GetArraySize(result);
    TAL_PR_NOTICE("swimming: role template list count=%d", count);

    INT_T i;
    for (i = 0; i < count; i++) {
        ty_cJSON *item = ty_cJSON_GetArrayItem(result, i);
        if (NULL == item) continue;

        ty_cJSON *template_id = ty_cJSON_GetObjectItem(item, "templateId");
        ty_cJSON *role_id = ty_cJSON_GetObjectItem(item, "roleId");
        ty_cJSON *role_name = ty_cJSON_GetObjectItem(item, "roleName");
        ty_cJSON *use_lang_code = ty_cJSON_GetObjectItem(item, "useLangCode");
        ty_cJSON *use_timbre_id = ty_cJSON_GetObjectItem(item, "useTimbreId");
        ty_cJSON *default_flag = ty_cJSON_GetObjectItem(item, "defaultFlag");

        TAL_PR_NOTICE("swimming: template[%d] roleId=%s, roleName=%s, useLangCode=%s, useTimbreId=%s, defaultFlag=%d",
                      i,
                      role_id ? role_id->valuestring : "null",
                      role_name ? role_name->valuestring : "null",
                      use_lang_code ? use_lang_code->valuestring : "null",
                      use_timbre_id ? use_timbre_id->valuestring : "null",
                      default_flag ? default_flag->valueint : 0);
    }

    TAL_PR_NOTICE("swimming: <<< get role template list done, count=%d", count);
    return result;
}

STATIC VOID swimming_get_timbre_market_all_pages(CHAR_T *lang, CHAR_T *out_voice_id, INT_T out_len)
{
    OPERATE_RET rt = OPRT_OK;
    ty_cJSON *result = NULL;
    INT_T page_no = 1;
    INT_T page_size = 20;
    INT_T total_printed = 0;
    BOOL_T first_saved = FALSE;
    CHAR_T post_content[256] = {0};

    TAL_PR_NOTICE("swimming: >>> get timbre market all pages, lang=%s", lang ? lang : "null");

    while (1) {
        if (lang && lang[0] != '\0') {
            snprintf(post_content, sizeof(post_content),
                     "{\"pageNo\":%d,\"pageSize\":%d,\"lang\":\"%s\"}",
                     page_no, page_size, lang);
        } else {
            snprintf(post_content, sizeof(post_content),
                     "{\"pageNo\":%d,\"pageSize\":%d}",
                     page_no, page_size);
        }

		bk_printf("swimming content = %s \r\n",post_content);
		
        result = NULL;
        rt = iot_httpc_common_post_simple("thing.ai.timbre.market.page", "1.0", post_content, NULL, &result);
        if (OPRT_OK != rt || NULL == result) {
            TAL_PR_ERR("swimming: get timbre market page %d failed, rt=%d", page_no, rt);
            break;
        }

		bk_printf("11111111111111111 \r\n");

        CHAR_T *result_str = ty_cJSON_PrintUnformatted(result);
        if (result_str) {
            TAL_PR_NOTICE("swimming: timbre market page %d raw result: %s", page_no, result_str);
            ty_cJSON_FreeBuffer(result_str);
        }

        ty_cJSON *list = NULL;
        INT_T total_page = 0;

        if (ty_cJSON_IsArray(result)) {
            list = result;
        } else {
            list = ty_cJSON_GetObjectItem(result, "list");
            ty_cJSON *tp = ty_cJSON_GetObjectItem(result, "totalPage");
            if (tp) total_page = tp->valueint;
        }

        if (NULL == list || !ty_cJSON_IsArray(list)) {
            TAL_PR_ERR("swimming: timbre market page %d list is null or not array", page_no);
            ty_cJSON_Delete(result);
            break;
        }

        INT_T count = ty_cJSON_GetArraySize(list);
        TAL_PR_NOTICE("swimming: timbre market page %d, count=%d, totalPage=%d", page_no, count, total_page);

        if (count == 0) {
            ty_cJSON_Delete(result);
            break;
        }

        INT_T i;
        for (i = 0; i < count; i++) {
            ty_cJSON *item = ty_cJSON_GetArrayItem(list, i);
            if (NULL == item) continue;

            ty_cJSON *voice_id = ty_cJSON_GetObjectItem(item, "voiceId");
            ty_cJSON *voice_name = ty_cJSON_GetObjectItem(item, "voiceName");
            ty_cJSON *speed = ty_cJSON_GetObjectItem(item, "speed");
            ty_cJSON *tone = ty_cJSON_GetObjectItem(item, "tone");
            ty_cJSON *demo_url = ty_cJSON_GetObjectItem(item, "demoUrl");
            ty_cJSON *desc_tags = ty_cJSON_GetObjectItem(item, "descTags");
            ty_cJSON *support_langs = ty_cJSON_GetObjectItem(item, "supportLangs");

            TAL_PR_NOTICE("swimming: timbre[%d] voiceId=%s, voiceName=%s",
                          total_printed,
                          voice_id ? voice_id->valuestring : "null",
                          voice_name ? voice_name->valuestring : "null");
            TAL_PR_NOTICE("swimming: timbre[%d] speed=%s, tone=%s",
                          total_printed,
                          speed ? speed->valuestring : "null",
                          tone ? tone->valuestring : "null");
            TAL_PR_NOTICE("swimming: timbre[%d] demoUrl=%s",
                          total_printed,
                          demo_url ? demo_url->valuestring : "null");
            TAL_PR_NOTICE("swimming: timbre[%d] descTags count=%d, supportLangs count=%d",
                          total_printed,
                          desc_tags && ty_cJSON_IsArray(desc_tags) ? ty_cJSON_GetArraySize(desc_tags) : 0,
                          support_langs && ty_cJSON_IsArray(support_langs) ? ty_cJSON_GetArraySize(support_langs) : 0);

            if (!first_saved && voice_id && voice_id->valuestring && out_voice_id) {
                snprintf(out_voice_id, out_len, "%s", voice_id->valuestring);
                first_saved = TRUE;
            }
            total_printed++;
        }

        ty_cJSON_Delete(result);

        if (total_page > 0 && page_no >= total_page) break;
        if (count < page_size) break;

        page_no++;
    }

    TAL_PR_NOTICE("swimming: <<< get timbre market done, total=%d, first voiceId=%s",
                  total_printed, out_voice_id ? out_voice_id : "null");
}

STATIC ty_cJSON *swimming_get_custom_role_page(VOID)
{
    OPERATE_RET rt = OPRT_OK;
    ty_cJSON *result = NULL;
    INT_T page_no = 1;
    INT_T page_size = 20;
    CHAR_T post_content[128] = {0};

    ty_cJSON *all_roles = ty_cJSON_CreateArray();
    if (NULL == all_roles) {
        TAL_PR_ERR("swimming: create custom role array failed");
        return NULL;
    }

    TAL_PR_NOTICE("swimming: >>> get custom role page");

    while (1) {
        snprintf(post_content, sizeof(post_content), "{\"pageNo\":%d,\"pageSize\":%d}", page_no, page_size);

        result = NULL;
        rt = iot_httpc_common_post_simple("thing.ai.agent.role.custom-role.page", "1.0", post_content, NULL, &result);
        if (OPRT_OK != rt || NULL == result) {
            TAL_PR_ERR("swimming: get custom role page %d failed, rt=%d", page_no, rt);
            break;
        }

        ty_cJSON *list = NULL;
        INT_T total_page = 0;

        if (ty_cJSON_IsArray(result)) {
            list = result;
        } else {
            list = ty_cJSON_GetObjectItem(result, "list");
            ty_cJSON *tp = ty_cJSON_GetObjectItem(result, "totalPage");
            if (tp) total_page = tp->valueint;
        }

        if (NULL == list || !ty_cJSON_IsArray(list)) {
            TAL_PR_ERR("swimming: custom role page %d list is null or not array", page_no);
            ty_cJSON_Delete(result);
            break;
        }

        INT_T count = ty_cJSON_GetArraySize(list);
        TAL_PR_NOTICE("swimming: custom role page %d, count=%d, totalPage=%d", page_no, count, total_page);

        if (count == 0) {
            ty_cJSON_Delete(result);
            break;
        }

        INT_T i;
        for (i = 0; i < count; i++) {
            ty_cJSON *item = ty_cJSON_GetArrayItem(list, i);
            if (NULL == item) continue;

            ty_cJSON *role_id = ty_cJSON_GetObjectItem(item, "roleId");
            ty_cJSON *role_name = ty_cJSON_GetObjectItem(item, "roleName");
            ty_cJSON *use_timbre_id = ty_cJSON_GetObjectItem(item, "useTimbreId");
            ty_cJSON *use_lang_code = ty_cJSON_GetObjectItem(item, "useLangCode");
            ty_cJSON *template_id = ty_cJSON_GetObjectItem(item, "templateId");

            TAL_PR_NOTICE("swimming: role[%d] roleId=%s, roleName=%s, useLangCode=%s, useTimbreId=%s, templateId=%s",
                          i,
                          role_id ? role_id->valuestring : "null",
                          role_name ? role_name->valuestring : "null",
                          use_lang_code ? use_lang_code->valuestring : "null",
                          use_timbre_id ? use_timbre_id->valuestring : "null",
                          template_id ? template_id->valuestring : "null");

            ty_cJSON *dup = ty_cJSON_Duplicate(item, 1);
            if (dup) {
                ty_cJSON_AddItemToArray(all_roles, dup);
            }
        }

        ty_cJSON_Delete(result);

        if (total_page > 0 && page_no >= total_page) break;
        if (count < page_size) break;

        page_no++;
    }

    INT_T total = ty_cJSON_GetArraySize(all_roles);
    TAL_PR_NOTICE("swimming: <<< get custom role done, total=%d", total);

    if (total == 0) {
        ty_cJSON_Delete(all_roles);
        return NULL;
    }

    return all_roles;
}

STATIC VOID swimming_create_custom_role_from_template(CHAR_T *role_id, CHAR_T *lang_code, CHAR_T *timbre_id)
{
    OPERATE_RET rt = OPRT_OK;
    ty_cJSON *result = NULL;
    CHAR_T post_content[512] = {0};

    if (NULL == role_id || role_id[0] == '\0') {
        TAL_PR_ERR("swimming: create from template failed, roleId is empty");
        return;
    }

    TAL_PR_NOTICE("swimming: >>> create custom role from template, roleId=%s, langCode=%s, timbreId=%s",
                  role_id, lang_code ? lang_code : "null", timbre_id ? timbre_id : "null");

    snprintf(post_content, sizeof(post_content),
             "{\"roleId\":\"%s\",\"useLangCode\":\"%s\",\"useTimbreId\":\"%s\",\"needBind\":true}",
             role_id,
             lang_code ? lang_code : "",
             timbre_id ? timbre_id : "");

    rt = iot_httpc_common_post_simple("thing.ai.agent.role.custom-role.create-from-template", "1.0", post_content, NULL, &result);
    if (OPRT_OK != rt) {
        TAL_PR_ERR("swimming: create from template failed, rt=%d", rt);
        if (result) ty_cJSON_Delete(result);
        return;
    }

    if (result) {
        CHAR_T *result_str = ty_cJSON_PrintUnformatted(result);
        if (result_str) {
            TAL_PR_NOTICE("swimming: create from template result: %s", result_str);
            ty_cJSON_FreeBuffer(result_str);
        }
        ty_cJSON_Delete(result);
    }

    TAL_PR_NOTICE("swimming: <<< create custom role from template success");
}

STATIC VOID swimming_update_custom_role(CHAR_T *role_id, CHAR_T *lang_code, CHAR_T *timbre_id)
{
    OPERATE_RET rt = OPRT_OK;
    ty_cJSON *result = NULL;
    CHAR_T post_content[512] = {0};

    if (NULL == role_id || role_id[0] == '\0') {
        TAL_PR_ERR("swimming: update custom role failed, roleId is empty");
        return;
    }

    TAL_PR_NOTICE("swimming: >>> update custom role, roleId=%s, langCode=%s, timbreId=%s",
                  role_id, lang_code ? lang_code : "null", timbre_id ? timbre_id : "null");

    snprintf(post_content, sizeof(post_content),
             "{\"roleId\":\"%s\",\"useLangCode\":\"%s\",\"useTimbreId\":\"%s\"}",
             role_id,
             lang_code ? lang_code : "",
             timbre_id ? timbre_id : "");

    rt = iot_httpc_common_post_simple("thing.ai.agent.role.custom-role.update", "1.0", post_content, NULL, &result);
    if (OPRT_OK != rt) {
        TAL_PR_ERR("swimming: update custom role failed, rt=%d", rt);
        if (result) ty_cJSON_Delete(result);
        return;
    }

    TAL_PR_NOTICE("swimming: <<< update custom role success");
    if (result) ty_cJSON_Delete(result);
}

STATIC VOID swimming_role_config_task(VOID *data)
{
    CHAR_T lang_code[64] = {0};
    CHAR_T timbre_id[128] = {0};
    CHAR_T model_role[128] = {0};//模版角色
	CHAR_T custum_role[128] = {0};//自定义角色

    //查询模板角色列表,返回完整JSON数组,调用方自行选择角色
    ty_cJSON *template_list = swimming_get_role_template_list();
    if (template_list) {
        // TODO: 由外部逻辑(如APP下发DP指令)决定选哪个角色,这里示例取第一个
        ty_cJSON *selected = ty_cJSON_GetArrayItem(template_list, 3);
        if (selected) {
            ty_cJSON *rid = ty_cJSON_GetObjectItem(selected, "roleId");
            if (rid && rid->valuestring) {
                snprintf(model_role, sizeof(model_role), "%s", rid->valuestring);
            }
        }
        ty_cJSON_Delete(template_list);
    }

    //查询所有的自定义角色列表,返回完整JSON数组,调用方自行选择角色
    ty_cJSON *custom_list = swimming_get_custom_role_page();
    if (custom_list) {
        // TODO: 由外部逻辑(如APP下发DP指令)决定选哪个角色,这里示例取第一个
        ty_cJSON *selected = ty_cJSON_GetArrayItem(custom_list, 0);
        if (selected) {
            ty_cJSON *rid = ty_cJSON_GetObjectItem(selected, "roleId");
            if (rid && rid->valuestring) {
                snprintf(custum_role, sizeof(custum_role), "%s", rid->valuestring);
            }
        }
        ty_cJSON_Delete(custom_list);
    }
	
    // 点击角色弹出语种选择(客户可以不从云端查,客户自己在设备侧配置也行)
    swimming_get_languages_list(lang_code, sizeof(lang_code));
	
    //查询支持该语种的音色列表(客户可以不从云端查,客户自己在设备侧配置也行)
    swimming_get_timbre_market_all_pages(lang_code, timbre_id, sizeof(timbre_id));

    //这个地方客户自行判断好逻辑,到底是自定义角色还是模版角色
    //如果是修改模板角色,调用根据模板角色创建自定义角色接口把语种带进去
    bk_printf("swimming model_role = %s,custum_role = %s \r\n",model_role,custum_role);
    swimming_create_custom_role_from_template(model_role, lang_code, timbre_id);

    //自定义角色则直接修改
    //swimming_update_custom_role(role_id, lang_code, timbre_id);
}