diff --git a/application/basic_demo/main/app_claw.c b/application/basic_demo/main/app_claw.c index a502a9a..d378ed5 100644 --- a/application/basic_demo/main/app_claw.c +++ b/application/basic_demo/main/app_claw.c @@ -383,7 +383,7 @@ esp_err_t app_claw_start(const basic_demo_settings_t *settings) core_config.append_session_turn = claw_memory_append_session_turn_callback; #endif core_config.call_cap = claw_cap_call_from_core; - core_config.task_stack_size = 6 * 1024; + core_config.task_stack_size = 16 * 1024; core_config.task_priority = 5; core_config.task_core = tskNO_AFFINITY; core_config.max_tool_iterations = 20; diff --git a/components/claw_capabilities/cap_im_feishu/src/cap_im_feishu.c b/components/claw_capabilities/cap_im_feishu/src/cap_im_feishu.c index e599f56..5793e36 100644 --- a/components/claw_capabilities/cap_im_feishu/src/cap_im_feishu.c +++ b/components/claw_capabilities/cap_im_feishu/src/cap_im_feishu.c @@ -18,10 +18,10 @@ #include "cap_im_attachment.h" #include "claw_cap.h" +#include "claw_task.h" #include "claw_event_publisher.h" #include "cJSON.h" #include "esp_crt_bundle.h" -#include "esp_heap_caps.h" #include "esp_event.h" #include "esp_http_client.h" #include "esp_log.h" @@ -54,22 +54,10 @@ #define CAP_IM_FEISHU_DEDUP_CACHE_SIZE 64 #define CAP_IM_FEISHU_RECONNECT_DELAY_MS 3000 #define CAP_IM_FEISHU_INITIAL_CONNECT_TIMEOUT_MS 15000 -#define CAP_IM_FEISHU_ATTACHMENT_STACK 8192 #define CAP_IM_FEISHU_ATTACHMENT_QUEUE_LEN 8 static const char *TAG = "cap_im_feishu"; -static UBaseType_t cap_im_feishu_task_memory_caps(void) -{ -#if defined(CONFIG_FREERTOS_TASK_CREATE_ALLOW_EXT_MEM) && CONFIG_FREERTOS_TASK_CREATE_ALLOW_EXT_MEM - if (heap_caps_get_total_size(MALLOC_CAP_SPIRAM) > 0) { - return MALLOC_CAP_SPIRAM; - } -#endif - - return MALLOC_CAP_INTERNAL; -} - typedef struct { char *buf; size_t len; @@ -1553,7 +1541,7 @@ static void cap_im_feishu_attachment_task(void *arg) } s_feishu.attachment_task = NULL; - vTaskDeleteWithCaps(NULL); + claw_task_delete(NULL); } static void cap_im_feishu_queue_attachment(const char *chat_id, @@ -1965,7 +1953,7 @@ static void cap_im_feishu_ws_task(void *arg) s_feishu.ws_client = NULL; s_feishu.ws_task = NULL; - vTaskDeleteWithCaps(NULL); + claw_task_delete(NULL); } static esp_err_t cap_im_feishu_gateway_init(void) @@ -2554,14 +2542,16 @@ esp_err_t cap_im_feishu_start(void) } } if (!s_feishu.attachment_task) { - ok = xTaskCreatePinnedToCoreWithCaps(cap_im_feishu_attachment_task, - "feishu_attach", - CAP_IM_FEISHU_ATTACHMENT_STACK, - NULL, - 5, - &s_feishu.attachment_task, - tskNO_AFFINITY, - cap_im_feishu_task_memory_caps()); + ok = claw_task_create(&(claw_task_config_t){ + .name = "feishu_attach", + .stack_size = 8192, + .priority = 5, + .core_id = tskNO_AFFINITY, + .stack_policy = CLAW_TASK_STACK_PREFER_PSRAM, + }, + cap_im_feishu_attachment_task, + NULL, + &s_feishu.attachment_task); if (ok != pdPASS) { vQueueDelete(s_feishu.attachment_queue); s_feishu.attachment_queue = NULL; @@ -2571,17 +2561,19 @@ esp_err_t cap_im_feishu_start(void) } s_feishu.stop_requested = false; - ok = xTaskCreatePinnedToCoreWithCaps(cap_im_feishu_ws_task, - "feishu_ws", - 8 * 1024, - NULL, - 5, - &s_feishu.ws_task, - tskNO_AFFINITY, - cap_im_feishu_task_memory_caps()); + ok = claw_task_create(&(claw_task_config_t){ + .name = "feishu_ws", + .stack_size = 8192, + .priority = 5, + .core_id = tskNO_AFFINITY, + .stack_policy = CLAW_TASK_STACK_PREFER_PSRAM, + }, + cap_im_feishu_ws_task, + NULL, + &s_feishu.ws_task); if (ok != pdPASS) { if (s_feishu.attachment_task) { - vTaskDeleteWithCaps(s_feishu.attachment_task); + claw_task_delete(s_feishu.attachment_task); s_feishu.attachment_task = NULL; } if (s_feishu.attachment_queue) { diff --git a/components/claw_capabilities/cap_im_qq/src/cap_im_qq.c b/components/claw_capabilities/cap_im_qq/src/cap_im_qq.c index b9eb099..eabf9cd 100644 --- a/components/claw_capabilities/cap_im_qq/src/cap_im_qq.c +++ b/components/claw_capabilities/cap_im_qq/src/cap_im_qq.c @@ -17,9 +17,9 @@ #include #include "cJSON.h" +#include "claw_task.h" #include "claw_event_publisher.h" #include "esp_crt_bundle.h" -#include "esp_heap_caps.h" #include "esp_http_client.h" #include "esp_log.h" #include "esp_timer.h" @@ -41,7 +41,6 @@ static const char *TAG = "cap_im_qq"; #define CAP_IM_QQ_HTTP_RESP_INIT 2048 #define CAP_IM_QQ_WS_TASK_STACK 6144 #define CAP_IM_QQ_WS_CLIENT_STACK 8192 -#define CAP_IM_QQ_INBOUND_TASK_STACK 8192 #define CAP_IM_QQ_WS_PRIO 5 #define CAP_IM_QQ_INBOUND_QUEUE_LEN 8 #define CAP_IM_QQ_DEDUP_CACHE_SIZE 64 @@ -68,17 +67,6 @@ static const char *TAG = "cap_im_qq"; #define BASIC_DEMO_QQ_APP_SECRET "" #endif -static UBaseType_t cap_im_qq_task_memory_caps(void) -{ -#if defined(CONFIG_FREERTOS_TASK_CREATE_ALLOW_EXT_MEM) && CONFIG_FREERTOS_TASK_CREATE_ALLOW_EXT_MEM - if (heap_caps_get_total_size(MALLOC_CAP_SPIRAM) > 0) { - return MALLOC_CAP_SPIRAM; - } -#endif - - return MALLOC_CAP_INTERNAL; -} - typedef struct { char *buf; size_t len; @@ -1065,7 +1053,7 @@ static void cap_im_qq_inbound_task(void *arg) } s_qq.inbound_task = NULL; - vTaskDeleteWithCaps(NULL); + claw_task_delete(NULL); } static void cap_im_qq_ws_event_handler(void *arg, @@ -1640,7 +1628,7 @@ static void cap_im_qq_ws_task(void *arg) s_qq.ws_task = NULL; s_qq.ws_client = NULL; - vTaskDeleteWithCaps(NULL); + claw_task_delete(NULL); } static void cap_im_qq_reset_runtime_state(void) @@ -1676,14 +1664,16 @@ static esp_err_t cap_im_qq_gateway_start(void) } } - ok = xTaskCreatePinnedToCoreWithCaps(cap_im_qq_inbound_task, - "qq_inbound", - CAP_IM_QQ_INBOUND_TASK_STACK, - NULL, - CAP_IM_QQ_WS_PRIO, - &s_qq.inbound_task, - tskNO_AFFINITY, - cap_im_qq_task_memory_caps()); + ok = claw_task_create(&(claw_task_config_t){ + .name = "qq_inbound", + .stack_size = 8192, + .priority = 5, + .core_id = tskNO_AFFINITY, + .stack_policy = CLAW_TASK_STACK_PREFER_PSRAM, + }, + cap_im_qq_inbound_task, + NULL, + &s_qq.inbound_task); if (ok != pdPASS) { vQueueDelete(s_qq.inbound_queue); s_qq.inbound_queue = NULL; @@ -1691,14 +1681,16 @@ static esp_err_t cap_im_qq_gateway_start(void) return ESP_FAIL; } - ok = xTaskCreatePinnedToCoreWithCaps(cap_im_qq_ws_task, - "qq_ws", - CAP_IM_QQ_WS_TASK_STACK, - NULL, - CAP_IM_QQ_WS_PRIO, - &s_qq.ws_task, - tskNO_AFFINITY, - cap_im_qq_task_memory_caps()); + ok = claw_task_create(&(claw_task_config_t){ + .name = "qq_ws", + .stack_size = 6144, + .priority = 5, + .core_id = tskNO_AFFINITY, + .stack_policy = CLAW_TASK_STACK_PREFER_PSRAM, + }, + cap_im_qq_ws_task, + NULL, + &s_qq.ws_task); if (ok != pdPASS) { s_qq.stop_requested = true; s_qq.ws_task = NULL; diff --git a/components/claw_capabilities/cap_im_tg/src/cap_im_tg.c b/components/claw_capabilities/cap_im_tg/src/cap_im_tg.c index 8483c0e..94145a2 100644 --- a/components/claw_capabilities/cap_im_tg/src/cap_im_tg.c +++ b/components/claw_capabilities/cap_im_tg/src/cap_im_tg.c @@ -17,9 +17,9 @@ #include #include "cJSON.h" +#include "claw_task.h" #include "claw_event_publisher.h" #include "esp_crt_bundle.h" -#include "esp_heap_caps.h" #include "esp_http_client.h" #include "esp_log.h" #include "esp_timer.h" @@ -35,9 +35,6 @@ static const char *TAG = "cap_im_tg"; #define CAP_IM_TG_MAX_MSG_LEN 4096 #define CAP_IM_TG_POLL_TIMEOUT_S 20 #define CAP_IM_TG_RETRY_DELAY_MS 3000 -#define CAP_IM_TG_TASK_STACK 6144 -#define CAP_IM_TG_ATTACHMENT_STACK 8192 -#define CAP_IM_TG_TASK_PRIO 5 #define CAP_IM_TG_ATTACHMENT_QUEUE_LEN 8 #define CAP_IM_TG_DEDUP_CACHE_SIZE 64 #define CAP_IM_TG_PATH_BUF_SIZE 256 @@ -48,17 +45,6 @@ static const char *TAG = "cap_im_tg"; #define BASIC_DEMO_TG_BOT_TOKEN "" #endif -static UBaseType_t cap_im_tg_task_memory_caps(void) -{ -#if defined(CONFIG_FREERTOS_TASK_CREATE_ALLOW_EXT_MEM) && CONFIG_FREERTOS_TASK_CREATE_ALLOW_EXT_MEM - if (heap_caps_get_total_size(MALLOC_CAP_SPIRAM) > 0) { - return MALLOC_CAP_SPIRAM; - } -#endif - - return MALLOC_CAP_INTERNAL; -} - typedef struct { char *buf; size_t len; @@ -558,7 +544,7 @@ static void cap_im_tg_attachment_task(void *arg) } s_tg.attachment_task = NULL; - vTaskDeleteWithCaps(NULL); + claw_task_delete(NULL); } static void cap_im_tg_queue_attachment(const char *chat_id, @@ -1127,7 +1113,7 @@ static void cap_im_tg_poll_task(void *arg) } s_tg.poll_task = NULL; - vTaskDeleteWithCaps(NULL); + claw_task_delete(NULL); } static esp_err_t cap_im_tg_gateway_init(void) @@ -1161,28 +1147,32 @@ static esp_err_t cap_im_tg_gateway_start(void) return ESP_ERR_NO_MEM; } } - ok = xTaskCreatePinnedToCoreWithCaps(cap_im_tg_attachment_task, - "tg_attach", - CAP_IM_TG_ATTACHMENT_STACK, - NULL, - CAP_IM_TG_TASK_PRIO, - &s_tg.attachment_task, - tskNO_AFFINITY, - cap_im_tg_task_memory_caps()); + ok = claw_task_create(&(claw_task_config_t){ + .name = "tg_attach", + .stack_size = 8192, + .priority = 5, + .core_id = tskNO_AFFINITY, + .stack_policy = CLAW_TASK_STACK_PREFER_PSRAM, + }, + cap_im_tg_attachment_task, + NULL, + &s_tg.attachment_task); if (ok != pdPASS) { vQueueDelete(s_tg.attachment_queue); s_tg.attachment_queue = NULL; s_tg.attachment_task = NULL; return ESP_FAIL; } - ok = xTaskCreatePinnedToCoreWithCaps(cap_im_tg_poll_task, - "tg_poll", - CAP_IM_TG_TASK_STACK, - NULL, - CAP_IM_TG_TASK_PRIO, - &s_tg.poll_task, - tskNO_AFFINITY, - cap_im_tg_task_memory_caps()); + ok = claw_task_create(&(claw_task_config_t){ + .name = "tg_poll", + .stack_size = 6144, + .priority = 5, + .core_id = tskNO_AFFINITY, + .stack_policy = CLAW_TASK_STACK_PREFER_PSRAM, + }, + cap_im_tg_poll_task, + NULL, + &s_tg.poll_task); if (ok != pdPASS) { s_tg.stop_requested = true; s_tg.poll_task = NULL; diff --git a/components/claw_capabilities/cap_im_wechat/src/cap_im_wechat.c b/components/claw_capabilities/cap_im_wechat/src/cap_im_wechat.c index 0bae179..13ed10a 100644 --- a/components/claw_capabilities/cap_im_wechat/src/cap_im_wechat.c +++ b/components/claw_capabilities/cap_im_wechat/src/cap_im_wechat.c @@ -17,10 +17,10 @@ #include "cap_im_attachment.h" #include "cJSON.h" #include "claw_cap.h" +#include "claw_task.h" #include "claw_event_publisher.h" #include "esp_crt_bundle.h" #include "esp_err.h" -#include "esp_heap_caps.h" #include "esp_http_client.h" #include "esp_log.h" #include "esp_random.h" @@ -40,8 +40,6 @@ static const char *TAG = "cap_im_wechat"; #define CAP_IM_WECHAT_MAX_MSG_LEN 4000 #define CAP_IM_WECHAT_POLL_TIMEOUT_MS 35000 #define CAP_IM_WECHAT_RETRY_DELAY_MS 2000 -#define CAP_IM_WECHAT_TASK_STACK 6144 -#define CAP_IM_WECHAT_TASK_PRIO 5 #define CAP_IM_WECHAT_DEDUP_CACHE_SIZE 64 #define CAP_IM_WECHAT_CONTEXT_CACHE_SIZE 32 #define CAP_IM_WECHAT_PATH_BUF_SIZE 256 @@ -121,17 +119,6 @@ typedef struct { cap_im_wechat_qr_state_t qr; } cap_im_wechat_state_t; -static UBaseType_t cap_im_wechat_task_memory_caps(void) -{ -#if defined(CONFIG_FREERTOS_TASK_CREATE_ALLOW_EXT_MEM) && CONFIG_FREERTOS_TASK_CREATE_ALLOW_EXT_MEM - if (heap_caps_get_total_size(MALLOC_CAP_SPIRAM) > 0) { - return MALLOC_CAP_SPIRAM; - } -#endif - - return MALLOC_CAP_INTERNAL; -} - static cap_im_wechat_state_t s_wechat = { .base_url = CAP_IM_WECHAT_DEFAULT_BASE_URL, .cdn_base_url = CAP_IM_WECHAT_DEFAULT_CDN_BASE_URL, @@ -1483,7 +1470,7 @@ static void cap_im_wechat_poll_task(void *arg) } s_wechat.poll_task = NULL; - vTaskDeleteWithCaps(NULL); + claw_task_delete(NULL); } static esp_err_t cap_im_wechat_send_message_json(cJSON *msg_root) @@ -2005,14 +1992,16 @@ static esp_err_t cap_im_wechat_gateway_start(void) } s_wechat.stop_requested = false; - ok = xTaskCreatePinnedToCoreWithCaps(cap_im_wechat_poll_task, - "wechat_poll", - CAP_IM_WECHAT_TASK_STACK, - NULL, - CAP_IM_WECHAT_TASK_PRIO, - &s_wechat.poll_task, - tskNO_AFFINITY, - cap_im_wechat_task_memory_caps()); + ok = claw_task_create(&(claw_task_config_t){ + .name = "wechat_poll", + .stack_size = 6144, + .priority = 5, + .core_id = tskNO_AFFINITY, + .stack_policy = CLAW_TASK_STACK_PREFER_PSRAM, + }, + cap_im_wechat_poll_task, + NULL, + &s_wechat.poll_task); if (ok != pdPASS) { s_wechat.poll_task = NULL; return ESP_FAIL; @@ -2178,14 +2167,16 @@ esp_err_t cap_im_wechat_qr_login_start(const char *account_id, bool force) } if (!s_wechat.qr_task) { - ok = xTaskCreatePinnedToCoreWithCaps(cap_im_wechat_qr_task, - "wechat_qr", - CAP_IM_WECHAT_TASK_STACK, - NULL, - CAP_IM_WECHAT_TASK_PRIO, - &s_wechat.qr_task, - tskNO_AFFINITY, - cap_im_wechat_task_memory_caps()); + ok = claw_task_create(&(claw_task_config_t){ + .name = "wechat_qr", + .stack_size = 6144, + .priority = 5, + .core_id = tskNO_AFFINITY, + .stack_policy = CLAW_TASK_STACK_PREFER_PSRAM, + }, + cap_im_wechat_qr_task, + NULL, + &s_wechat.qr_task); if (ok != pdPASS) { s_wechat.qr.active = false; s_wechat.qr_task = NULL; @@ -2599,5 +2590,5 @@ static void cap_im_wechat_qr_task(void *arg) vTaskDelay(pdMS_TO_TICKS(1000)); } - vTaskDeleteWithCaps(NULL); + claw_task_delete(NULL); } diff --git a/components/claw_capabilities/cap_lua/src/cap_lua_async.c b/components/claw_capabilities/cap_lua/src/cap_lua_async.c index 5f71a26..50a83bf 100644 --- a/components/claw_capabilities/cap_lua/src/cap_lua_async.c +++ b/components/claw_capabilities/cap_lua/src/cap_lua_async.c @@ -11,6 +11,7 @@ #include #include +#include "claw_task.h" #include "esp_heap_caps.h" #include "esp_log.h" #include "esp_random.h" @@ -54,12 +55,6 @@ static SemaphoreHandle_t s_slot_terminal_sem[CAP_LUA_ASYNC_MAX_JOBS]; static size_t s_running_jobs; static bool s_runner_started; -static UBaseType_t cap_lua_task_memory_caps(void) -{ - /* Lua async jobs touch FATFS/flash-backed files, so their stacks must stay internal. */ - return MALLOC_CAP_INTERNAL; -} - const char *cap_lua_job_status_name(cap_lua_job_status_t status) { switch (status) { @@ -233,7 +228,7 @@ static void cap_lua_job_task(void *arg) esp_err_t err; if (!ctx) { - vTaskDeleteWithCaps(NULL); + claw_task_delete(NULL); return; } @@ -242,7 +237,7 @@ static void cap_lua_job_task(void *arg) cap_lua_finish_job(ctx, false, NULL, "failed to allocate output buffer"); free(ctx->args_json); free(ctx); - vTaskDeleteWithCaps(NULL); + claw_task_delete(NULL); return; } @@ -256,7 +251,7 @@ static void cap_lua_job_task(void *arg) free(output); free(ctx->args_json); free(ctx); - vTaskDeleteWithCaps(NULL); + claw_task_delete(NULL); } esp_err_t cap_lua_async_init(void) @@ -653,40 +648,43 @@ static esp_err_t cap_lua_async_submit_once(const cap_lua_async_job_t *job, s_running_jobs++; xSemaphoreGive(s_job_lock); - if (xTaskCreatePinnedToCoreWithCaps(cap_lua_job_task, - "cap_lua_async", - CAP_LUA_ASYNC_STACK, - ctx, - CAP_LUA_ASYNC_PRIO, - &s_jobs[slot].task_handle, - tskNO_AFFINITY, - cap_lua_task_memory_caps()) != pdPASS) { - UBaseType_t caps = cap_lua_task_memory_caps(); - size_t free_bytes = heap_caps_get_free_size(caps); - size_t largest = heap_caps_get_largest_free_block(caps); - ESP_LOGE(TAG, "Failed to spawn task (stack=%u, free=%u, largest_block=%u)", - (unsigned)CAP_LUA_ASYNC_STACK, - (unsigned)free_bytes, - (unsigned)largest); - if (err_out && err_out_size > 0) { - snprintf(err_out, err_out_size, - "Out of internal RAM for Lua task stack " - "(need %u bytes, largest free block %u bytes, total free %u bytes). " - "Stop another job and retry.", - (unsigned)CAP_LUA_ASYNC_STACK, - (unsigned)largest, - (unsigned)free_bytes); - } - if (xSemaphoreTake(s_job_lock, pdMS_TO_TICKS(1000)) == pdTRUE) { - cap_lua_clear_slot(&s_jobs[slot]); - if (s_running_jobs > 0) { - s_running_jobs--; + { + claw_task_config_t task_config = { + .name = "cap_lua_async", + .stack_size = 12 * 1024, + .priority = 4, + .core_id = tskNO_AFFINITY, + .stack_policy = CLAW_TASK_STACK_PREFER_PSRAM, + }; + + if (claw_task_create(&task_config, cap_lua_job_task, ctx, &s_jobs[slot].task_handle) != pdPASS) { + size_t free_bytes = heap_caps_get_free_size(MALLOC_CAP_INTERNAL); + size_t largest = heap_caps_get_largest_free_block(MALLOC_CAP_INTERNAL); + + ESP_LOGE(TAG, "Failed to spawn task (stack=%u, free=%u, largest_block=%u)", + (unsigned)task_config.stack_size, + (unsigned)free_bytes, + (unsigned)largest); + if (err_out && err_out_size > 0) { + snprintf(err_out, err_out_size, + "Out of task memory for Lua task stack " + "(need %u bytes, largest free block %u bytes, total free %u bytes). " + "Stop another job and retry.", + (unsigned)task_config.stack_size, + (unsigned)largest, + (unsigned)free_bytes); } - xSemaphoreGive(s_job_lock); + if (xSemaphoreTake(s_job_lock, pdMS_TO_TICKS(1000)) == pdTRUE) { + cap_lua_clear_slot(&s_jobs[slot]); + if (s_running_jobs > 0) { + s_running_jobs--; + } + xSemaphoreGive(s_job_lock); + } + free(ctx->args_json); + free(ctx); + return ESP_ERR_NO_MEM; } - free(ctx->args_json); - free(ctx); - return ESP_ERR_NO_MEM; } if (xSemaphoreTake(s_job_lock, pdMS_TO_TICKS(1000)) == pdTRUE) { diff --git a/components/claw_capabilities/cap_lua/src/cap_lua_internal.h b/components/claw_capabilities/cap_lua/src/cap_lua_internal.h index 7a46c70..0e48c60 100644 --- a/components/claw_capabilities/cap_lua/src/cap_lua_internal.h +++ b/components/claw_capabilities/cap_lua/src/cap_lua_internal.h @@ -20,8 +20,6 @@ #define CAP_LUA_ASYNC_DEFAULT_TIMEOUT_MS 0 #define CAP_LUA_ASYNC_MAX_JOBS 16 #define CAP_LUA_ASYNC_MAX_CONCURRENT 4 -#define CAP_LUA_ASYNC_STACK (12 * 1024) -#define CAP_LUA_ASYNC_PRIO 4 #define CAP_LUA_MAX_MODULES 16 #define CAP_LUA_JOB_NAME_MAX 32 diff --git a/components/claw_capabilities/cap_scheduler/src/cap_scheduler.c b/components/claw_capabilities/cap_scheduler/src/cap_scheduler.c index 1dc5cf6..a516105 100644 --- a/components/claw_capabilities/cap_scheduler/src/cap_scheduler.c +++ b/components/claw_capabilities/cap_scheduler/src/cap_scheduler.c @@ -10,8 +10,10 @@ #include "cap_scheduler_internal.h" #include "claw_cap.h" +#include "claw_task.h" #include "esp_check.h" #include "esp_log.h" +#include "freertos/idf_additions.h" static const char *TAG = "cap_scheduler"; @@ -568,7 +570,7 @@ static void cap_scheduler_task(void *arg) } s_cap_scheduler.started = false; s_cap_scheduler.task_handle = NULL; - vTaskDelete(NULL); + claw_task_delete(NULL); } static esp_err_t cap_scheduler_load_from_disk_locked(void) @@ -809,6 +811,13 @@ esp_err_t cap_scheduler_init(const cap_scheduler_config_t *config) esp_err_t cap_scheduler_start(void) { BaseType_t ok; + claw_task_config_t task_config = { + .name = "cap_scheduler", + .stack_size = s_cap_scheduler.config.task_stack_size, + .priority = s_cap_scheduler.config.task_priority, + .core_id = s_cap_scheduler.config.task_core, + .stack_policy = CLAW_TASK_STACK_PREFER_PSRAM, + }; if (!s_cap_scheduler.initialized) { return ESP_ERR_INVALID_STATE; @@ -818,22 +827,7 @@ esp_err_t cap_scheduler_start(void) } s_cap_scheduler.stop_requested = false; - if (s_cap_scheduler.config.task_core == tskNO_AFFINITY) { - ok = xTaskCreate(cap_scheduler_task, - "cap_scheduler", - s_cap_scheduler.config.task_stack_size, - NULL, - s_cap_scheduler.config.task_priority, - &s_cap_scheduler.task_handle); - } else { - ok = xTaskCreatePinnedToCore(cap_scheduler_task, - "cap_scheduler", - s_cap_scheduler.config.task_stack_size, - NULL, - s_cap_scheduler.config.task_priority, - &s_cap_scheduler.task_handle, - s_cap_scheduler.config.task_core); - } + ok = claw_task_create(&task_config, cap_scheduler_task, NULL, &s_cap_scheduler.task_handle); if (ok != pdPASS) { s_cap_scheduler.task_handle = NULL; return ESP_FAIL; diff --git a/components/claw_capabilities/cap_system/src/cap_system.c b/components/claw_capabilities/cap_system/src/cap_system.c index d8690f1..e14549f 100644 --- a/components/claw_capabilities/cap_system/src/cap_system.c +++ b/components/claw_capabilities/cap_system/src/cap_system.c @@ -13,21 +13,20 @@ #include "cJSON.h" #include "claw_cap.h" +#include "claw_task.h" #include "esp_chip_info.h" #include "esp_err.h" -#include "esp_heap_caps.h" #include "esp_log.h" #include "esp_netif.h" #include "esp_system.h" #include "esp_timer.h" #include "esp_wifi.h" #include "freertos/FreeRTOS.h" +#include "freertos/idf_additions.h" #include "freertos/task.h" static const char *TAG = "cap_system"; -#define CAP_SYSTEM_RESTART_TASK_STACK_SIZE 3072 -#define CAP_SYSTEM_RESTART_TASK_PRIORITY 5 #define CAP_SYSTEM_RESTART_DEFAULT_DELAY_MS 500 #ifdef CONFIG_CLAW_CAP_SYSTEM_DEBUG_LOGS @@ -406,12 +405,16 @@ static esp_err_t cap_system_restart_async(uint32_t delay_ms) task_args->delay_ms = delay_ms; // Restart is deferred to let the current response flush out first. - ok = xTaskCreate(cap_system_restart_task, - "cap_system_restart", - CAP_SYSTEM_RESTART_TASK_STACK_SIZE, - task_args, - CAP_SYSTEM_RESTART_TASK_PRIORITY, - NULL); + ok = claw_task_create(&(claw_task_config_t){ + .name = "cap_system_restart", + .stack_size = 3072, + .priority = 5, + .core_id = tskNO_AFFINITY, + .stack_policy = CLAW_TASK_STACK_PREFER_PSRAM, + }, + cap_system_restart_task, + task_args, + NULL); if (ok != pdPASS) { free(task_args); ESP_LOGE(TAG, "restart task create failed"); diff --git a/components/claw_capabilities/cap_time/src/cap_time.c b/components/claw_capabilities/cap_time/src/cap_time.c index 49ec895..c1b85b2 100644 --- a/components/claw_capabilities/cap_time/src/cap_time.c +++ b/components/claw_capabilities/cap_time/src/cap_time.c @@ -13,11 +13,13 @@ #include #include "claw_cap.h" +#include "claw_task.h" #include "esp_check.h" #include "esp_crt_bundle.h" #include "esp_http_client.h" #include "esp_log.h" #include "freertos/FreeRTOS.h" +#include "freertos/idf_additions.h" #include "freertos/semphr.h" #include "freertos/task.h" @@ -29,8 +31,6 @@ static const char *TAG = "cap_time"; #define CAP_TIME_UTC_TIMEZONE "UTC0" #define CAP_TIME_DEFAULT_DISCONNECTED_RETRY_MS 5000 #define CAP_TIME_DEFAULT_SYNC_RETRY_MS 30000 -#define CAP_TIME_SYNC_TASK_STACK_SIZE 4096 -#define CAP_TIME_SYNC_TASK_PRIORITY 5 static const char *s_month_names[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", @@ -321,7 +321,7 @@ static void cap_time_sync_service_task(void *arg) s_time_service.running = false; s_time_service.task_handle = NULL; - vTaskDelete(NULL); + claw_task_delete(NULL); } static esp_err_t cap_time_execute(const char *input_json, @@ -404,12 +404,16 @@ esp_err_t cap_time_sync_service_start(const cap_time_sync_service_config_t *conf s_time_service.running = true; - ok = xTaskCreate(cap_time_sync_service_task, - "cap_time_sync", - CAP_TIME_SYNC_TASK_STACK_SIZE, - NULL, - CAP_TIME_SYNC_TASK_PRIORITY, - &s_time_service.task_handle); + ok = claw_task_create(&(claw_task_config_t){ + .name = "cap_time_sync", + .stack_size = 4096, + .priority = 5, + .core_id = tskNO_AFFINITY, + .stack_policy = CLAW_TASK_STACK_PREFER_PSRAM, + }, + cap_time_sync_service_task, + NULL, + &s_time_service.task_handle); if (ok != pdPASS || !s_time_service.task_handle) { s_time_service.running = false; s_time_service.task_handle = NULL; diff --git a/components/claw_modules/claw_core/CMakeLists.txt b/components/claw_modules/claw_core/CMakeLists.txt index 2a5d037..486edcf 100644 --- a/components/claw_modules/claw_core/CMakeLists.txt +++ b/components/claw_modules/claw_core/CMakeLists.txt @@ -1,6 +1,7 @@ idf_component_register( SRCS "src/claw_core.c" + "src/claw_task.c" "src/claw_core_llm.c" "src/llm/claw_llm_runtime.c" "src/llm/claw_llm_http_transport.c" @@ -14,6 +15,7 @@ idf_component_register( REQUIRES esp_http_client esp-tls + freertos json mbedtls PRIV_REQUIRES diff --git a/components/claw_modules/claw_core/include/claw_task.h b/components/claw_modules/claw_core/include/claw_task.h new file mode 100644 index 0000000..14980c5 --- /dev/null +++ b/components/claw_modules/claw_core/include/claw_task.h @@ -0,0 +1,67 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#pragma once + +#include "esp_err.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + CLAW_TASK_STACK_INTERNAL_ONLY = 0, + CLAW_TASK_STACK_PREFER_PSRAM, + CLAW_TASK_STACK_PSRAM_ONLY, +} claw_task_stack_policy_t; + +typedef struct { + const char *name; + uint32_t stack_size; + UBaseType_t priority; + BaseType_t core_id; + claw_task_stack_policy_t stack_policy; +} claw_task_config_t; + +/** + * @brief Creates a task using the provided config, optionally overridden by the internal task config table + * + * This function is similar to xTaskCreatePinnedToCoreWithCaps(), except that it + * first resolves the caller-provided config against the internal override table. + * If an entry with the same task name exists in the override table, that entry + * is used as the effective task config. Otherwise, the caller-provided config is + * used as-is. + * + * The selected stack policy controls whether the task stack is allocated from + * internal RAM or PSRAM. However, the selected memory capabilities will NOT + * apply to the task's TCB as a TCB must always be in internal RAM. + * + * @param config Pointer to the caller-provided task config + * @param task_func Pointer to the task entry function + * @param arg Pointer that will be used as the parameter for the task being created + * @param task_handle Used to pass back a handle by which the created task can be referenced + * @return pdPASS if the task was successfully created and added to a ready list, + * otherwise an error code defined in the file projdefs.h + */ +BaseType_t claw_task_create(const claw_task_config_t *config, + TaskFunction_t task_func, + void *arg, + TaskHandle_t *task_handle); + +/** + * @brief Deletes a task created through xTaskCreatePinnedToCoreWithCaps() + * + * This function is similar to vTaskDeleteWithCaps() and should be used to + * delete tasks whose stacks were allocated with explicit memory capabilities. + * + * @param task_handle Handle of the task to delete, or NULL to delete the calling task + */ +void claw_task_delete(TaskHandle_t task_handle); + +#ifdef __cplusplus +} +#endif diff --git a/components/claw_modules/claw_core/src/claw_core.c b/components/claw_modules/claw_core/src/claw_core.c index 8b108ad..373b368 100644 --- a/components/claw_modules/claw_core/src/claw_core.c +++ b/components/claw_modules/claw_core/src/claw_core.c @@ -4,6 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ #include "claw_core.h" +#include "claw_task.h" #include #include @@ -14,7 +15,9 @@ #include #include "cJSON.h" +#include "esp_heap_caps.h" #include "esp_log.h" +#include "freertos/idf_additions.h" #include "freertos/queue.h" #include "freertos/semphr.h" #include "freertos/task.h" @@ -1395,22 +1398,16 @@ esp_err_t claw_core_start(void) return ESP_OK; } - if (s_core.task_core == tskNO_AFFINITY) { - task_result = xTaskCreate(claw_core_task, - "claw_core", - s_core.task_stack_size, - NULL, - s_core.task_priority, - &s_core.task_handle); - } else { - task_result = xTaskCreatePinnedToCore(claw_core_task, - "claw_core", - s_core.task_stack_size, - NULL, - s_core.task_priority, - &s_core.task_handle, - s_core.task_core); - } + task_result = claw_task_create(&(claw_task_config_t){ + .name = "claw_core", + .stack_size = s_core.task_stack_size, + .priority = s_core.task_priority, + .core_id = s_core.task_core, + .stack_policy = CLAW_TASK_STACK_PREFER_PSRAM, + }, + claw_core_task, + NULL, + &s_core.task_handle); if (task_result != pdPASS) { return ESP_FAIL; diff --git a/components/claw_modules/claw_core/src/claw_task.c b/components/claw_modules/claw_core/src/claw_task.c new file mode 100644 index 0000000..6b45b91 --- /dev/null +++ b/components/claw_modules/claw_core/src/claw_task.c @@ -0,0 +1,112 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include "claw_task.h" + +#include +#include +#include + +#include "esp_heap_caps.h" +#include "esp_log.h" +#include "freertos/idf_additions.h" + +static const char *TAG = "claw_task"; + +/* Entries in this table override the config passed by the caller. Keep a trailing sentinel so the table can be left effectively empty. */ +static const claw_task_config_t s_task_configs[] = { + {0}, +}; + +static const claw_task_config_t *claw_task_find_override(const char *name) +{ + size_t i; + + if (!name || !name[0]) { + return NULL; + } + + for (i = 0; s_task_configs[i].name; i++) { + if (strcmp(s_task_configs[i].name, name) == 0) { + return &s_task_configs[i]; + } + } + + return NULL; +} + +static bool claw_task_external_memory_available(void) +{ +#if defined(CONFIG_FREERTOS_TASK_CREATE_ALLOW_EXT_MEM) && CONFIG_FREERTOS_TASK_CREATE_ALLOW_EXT_MEM + return heap_caps_get_total_size(MALLOC_CAP_SPIRAM) > 0; +#else + return false; +#endif +} + +static esp_err_t claw_task_resolve_config(const claw_task_config_t *input, claw_task_config_t *out) +{ + const claw_task_config_t *override = NULL; + + if (!input || !input->name || !input->name[0] || !out) { + return ESP_ERR_INVALID_ARG; + } + + *out = *input; + override = claw_task_find_override(input->name); + if (override) { + *out = *override; + } + + return ESP_OK; +} + +static UBaseType_t claw_task_memory_caps(claw_task_stack_policy_t policy) +{ + if (policy != CLAW_TASK_STACK_INTERNAL_ONLY && claw_task_external_memory_available()) { + return MALLOC_CAP_SPIRAM; + } + + return MALLOC_CAP_INTERNAL; +} + +BaseType_t claw_task_create(const claw_task_config_t *config, + TaskFunction_t task_func, + void *arg, + TaskHandle_t *task_handle) +{ + claw_task_config_t resolved = {0}; + UBaseType_t memory_caps; + + if (claw_task_resolve_config(config, &resolved) != ESP_OK || !task_func || resolved.stack_size == 0) { + return errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY; + } + + memory_caps = claw_task_memory_caps(resolved.stack_policy); + if (resolved.stack_policy == CLAW_TASK_STACK_PSRAM_ONLY && memory_caps != MALLOC_CAP_SPIRAM) { + ESP_LOGE(TAG, "task '%s' requires PSRAM stack but PSRAM is unavailable", resolved.name); + return errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY; + } + ESP_LOGD(TAG, "Creating task '%s' with stack size %u, priority %u, on core %d, with memory caps %s\n", + resolved.name, + (unsigned int)resolved.stack_size, + (unsigned int)resolved.priority, + resolved.core_id, + memory_caps == MALLOC_CAP_SPIRAM ? "SPIRAM" : "INTERNAL"); + + return xTaskCreatePinnedToCoreWithCaps(task_func, + resolved.name, + resolved.stack_size, + arg, + resolved.priority, + task_handle, + resolved.core_id, + memory_caps); +} + +void claw_task_delete(TaskHandle_t task_handle) +{ + vTaskDeleteWithCaps(task_handle); +} diff --git a/components/claw_modules/claw_event_router/src/claw_event_router.c b/components/claw_modules/claw_event_router/src/claw_event_router.c index dc09d0b..70935ab 100644 --- a/components/claw_modules/claw_event_router/src/claw_event_router.c +++ b/components/claw_modules/claw_event_router/src/claw_event_router.c @@ -19,7 +19,10 @@ #include "cJSON.h" #include "claw_core.h" #include "claw_event_publisher.h" +#include "claw_task.h" +#include "esp_heap_caps.h" #include "esp_log.h" +#include "freertos/idf_additions.h" #include "freertos/queue.h" #include "freertos/semphr.h" #include "freertos/task.h" @@ -2028,7 +2031,7 @@ static void claw_event_router_task(void *arg) s_runtime.task_handle = NULL; s_runtime.started = false; - vTaskDelete(NULL); + claw_task_delete(NULL); } esp_err_t claw_event_router_init(const claw_event_router_config_t *config) @@ -2106,22 +2109,16 @@ esp_err_t claw_event_router_start(void) s_runtime.config.core_receive_timeout_ms : CLAW_EVENT_ROUTER_DEFAULT_RECEIVE; s_runtime.stop_requested = false; - if (core == tskNO_AFFINITY) { - task_ok = xTaskCreate(claw_event_router_task, - "event_router", - stack_size, - NULL, - priority, - &s_runtime.task_handle); - } else { - task_ok = xTaskCreatePinnedToCore(claw_event_router_task, - "event_router", - stack_size, - NULL, - priority, - &s_runtime.task_handle, - core); - } + task_ok = claw_task_create(&(claw_task_config_t){ + .name = "event_router", + .stack_size = stack_size, + .priority = priority, + .core_id = core, + .stack_policy = CLAW_TASK_STACK_PREFER_PSRAM, + }, + claw_event_router_task, + NULL, + &s_runtime.task_handle); if (task_ok != pdPASS) { s_runtime.task_handle = NULL; return ESP_FAIL; diff --git a/components/claw_modules/claw_memory/src/claw_memory_session.c b/components/claw_modules/claw_memory/src/claw_memory_session.c index fd63fc9..e8145e9 100644 --- a/components/claw_modules/claw_memory/src/claw_memory_session.c +++ b/components/claw_modules/claw_memory/src/claw_memory_session.c @@ -4,6 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ #include "claw_memory_internal.h" +#include "claw_task.h" #include #include @@ -12,6 +13,8 @@ #include #include "esp_log.h" +#include "esp_heap_caps.h" +#include "freertos/idf_additions.h" #include "freertos/queue.h" #include "freertos/semphr.h" #include "freertos/task.h" @@ -378,7 +381,7 @@ static void claw_memory_async_extract_deinit(void) job = next; } if (s_async_extract.task_handle) { - vTaskDelete(s_async_extract.task_handle); + claw_task_delete(s_async_extract.task_handle); s_async_extract.task_handle = NULL; } if (s_async_extract.queue) { @@ -447,12 +450,16 @@ esp_err_t claw_memory_async_extract_init(const claw_memory_config_t *config) return ESP_ERR_NO_MEM; } - task_result = xTaskCreate(claw_memory_async_extract_task, - "claw_mem_extract", - CLAW_MEMORY_ASYNC_EXTRACT_STACK_SIZE, - NULL, - CLAW_MEMORY_ASYNC_EXTRACT_PRIORITY, - &s_async_extract.task_handle); + task_result = claw_task_create(&(claw_task_config_t){ + .name = "claw_mem_extract", + .stack_size = CLAW_MEMORY_ASYNC_EXTRACT_STACK_SIZE, + .priority = CLAW_MEMORY_ASYNC_EXTRACT_PRIORITY, + .core_id = tskNO_AFFINITY, + .stack_policy = CLAW_TASK_STACK_PREFER_PSRAM, + }, + claw_memory_async_extract_task, + NULL, + &s_async_extract.task_handle); if (task_result != pdPASS) { claw_memory_async_extract_deinit(); return ESP_FAIL;