diff --git a/apps/mimiclaw/CMakeLists.txt b/apps/mimiclaw/CMakeLists.txt index c9c6ca683..894a108d2 100755 --- a/apps/mimiclaw/CMakeLists.txt +++ b/apps/mimiclaw/CMakeLists.txt @@ -28,8 +28,10 @@ set(APP_SRCS ${APP_PATH}/tools/tool_registry.c ${APP_PATH}/tools/tool_cron.c ${APP_PATH}/tools/tool_web_search.c + ${APP_PATH}/tools/tool_web_search_baidu.c ${APP_PATH}/tools/tool_get_time.c ${APP_PATH}/tools/tool_files.c + ${APP_PATH}/tools/tool_led.c ${APP_PATH}/skills/skill_loader.c ) diff --git a/apps/mimiclaw/README_zh.md b/apps/mimiclaw/README_zh.md index 75778976d..5ef3c8558 100644 --- a/apps/mimiclaw/README_zh.md +++ b/apps/mimiclaw/README_zh.md @@ -151,6 +151,30 @@ cp apps/tuya_mimiclaw/mimi_secrets.h.example apps/tuya_mimiclaw/mimi_secrets.h 5. **也可以直接复制 `apps/tuy_mimiclaw/mimi_secrets.h.example` 并重命名为 `mimi_secrets.h` 修改文件上的配置,并重新编译** (本仓库中目录名为 `apps/tuya_mimiclaw`,请以实际路径为准) +6. **存储切换** + +在 `apps/tuya_mimiclaw/mimi_config.h` 中 + +``` +#if MIMI_USE_SDCARD +#define MIMI_FS_BASE "/spiffs" +#else +#define MIMI_FS_BASE "/spiffs" +#endif +```c + +7. **搜索引擎切换** + +在 `apps/tuya_mimiclaw/mimi_config.h` 中 + +``` +/* Search Engine Selection: 0 = Brave, 1 = Baidu */ +#ifndef MIMI_USE_BAIDU_SEARCH +#define MIMI_USE_BAIDU_SEARCH 0 +#endif +```c + + ### 6. 配置优先级规则 配置优先级(从高到低)如下: diff --git a/apps/mimiclaw/agent/agent_loop.c b/apps/mimiclaw/agent/agent_loop.c index 878497a6e..23ac2b90e 100644 --- a/apps/mimiclaw/agent/agent_loop.c +++ b/apps/mimiclaw/agent/agent_loop.c @@ -120,8 +120,11 @@ static char *patch_tool_input_with_context(const llm_tool_call_t *call, const mi changed = true; } - if (channel && strcmp(channel, MIMI_CHAN_TELEGRAM) == 0 && strcmp(msg->channel, MIMI_CHAN_TELEGRAM) == 0 && - msg->chat_id[0] != '\0') { + /* Patch chat_id for Telegram and Feishu if AI used invalid 'cron' placeholder */ + bool same_channel = channel && msg->channel[0] != '\0' && strcmp(channel, msg->channel) == 0; + bool needs_patch = + same_channel && (strcmp(channel, MIMI_CHAN_TELEGRAM) == 0 || strcmp(channel, MIMI_CHAN_FEISHU) == 0); + if (needs_patch && msg->chat_id[0] != '\0') { cJSON *chat_item = cJSON_GetObjectItem(root, "chat_id"); const char *chat_id = cJSON_IsString(chat_item) ? chat_item->valuestring : NULL; if (!chat_id || chat_id[0] == '\0' || strcmp(chat_id, "cron") == 0) { @@ -160,13 +163,20 @@ static cJSON *build_tool_results(const llm_response_t *resp, const mimi_msg_t *m tool_output[0] = '\0'; + MIMI_LOGI(TAG, ">>> TOOL EXEC: name=%s id=%s input=%s", call->name, call->id, + tool_input ? tool_input : "(null)"); OPERATE_RET rt = tool_registry_execute(call->name, tool_input, tool_output, tool_output_size); if (rt != OPRT_OK && tool_output[0] == '\0') { snprintf(tool_output, tool_output_size, "Tool execute failed: %d", rt); } cJSON_free(patched_input); - MIMI_LOGI(TAG, "tool=%s result_bytes=%u", call->name, (unsigned)strlen(tool_output)); + MIMI_LOGI(TAG, "<<< TOOL RESULT: name=%s rt=%d result_bytes=%u", call->name, rt, (unsigned)strlen(tool_output)); + if (strlen(tool_output) <= 512) { + MIMI_LOGI(TAG, " tool output: %s", tool_output); + } else { + MIMI_LOGI(TAG, " tool output (first 512): %.512s ...[truncated]", tool_output); + } cJSON *result_block = cJSON_CreateObject(); if (!result_block) { @@ -212,7 +222,9 @@ static void agent_loop_task(void *arg) continue; } - MIMI_LOGI(TAG, "processing msg %s:%s", in_msg.channel, in_msg.chat_id); + MIMI_LOGI(TAG, "================ AGENT TURN START ================"); + MIMI_LOGI(TAG, "processing msg channel=%s chat_id=%s", in_msg.channel, in_msg.chat_id); + MIMI_LOGI(TAG, "user input: %s", in_msg.content ? in_msg.content : "(null)"); if (context_build_system_prompt(system_prompt, MIMI_CONTEXT_BUF_SIZE) != OPRT_OK) { free(in_msg.content); @@ -261,22 +273,35 @@ static void agent_loop_task(void *arg) } #endif + MIMI_LOGI(TAG, "--- LLM call iteration=%d ---", iteration + 1); + bool force_tool = (iteration == 0); /* first iteration: force tool use */ llm_response_t resp; - OPERATE_RET rt = llm_chat_tools(system_prompt, messages, tools_json, &resp); + OPERATE_RET rt = llm_chat_tools_ex(system_prompt, messages, tools_json, force_tool, &resp); if (rt != OPRT_OK) { - MIMI_LOGE(TAG, "llm_chat_tools failed: %d", rt); + MIMI_LOGE(TAG, "llm_chat_tools failed: rt=%d iteration=%d", rt, iteration + 1); break; } + MIMI_LOGI(TAG, "LLM response: tool_use=%s text_len=%u call_count=%d", resp.tool_use ? "true" : "false", + (unsigned)(resp.text ? strlen(resp.text) : 0), resp.call_count); + if (!resp.tool_use) { if (resp.text && resp.text[0] != '\0') { final_text = strdup(resp.text); + MIMI_LOGI(TAG, "final text (len=%u): %.512s%s", (unsigned)strlen(resp.text), resp.text, + strlen(resp.text) > 512 ? "...[truncated]" : ""); + } else { + MIMI_LOGW(TAG, "LLM returned no text and no tool calls"); } llm_response_free(&resp); break; } MIMI_LOGI(TAG, "tool iteration=%d call_count=%d", iteration + 1, resp.call_count); + for (int tc_i = 0; tc_i < resp.call_count; tc_i++) { + MIMI_LOGI(TAG, " tool_call[%d]: name=%s id=%s input=%s", tc_i, resp.calls[tc_i].name, + resp.calls[tc_i].id, resp.calls[tc_i].input ? resp.calls[tc_i].input : "(null)"); + } cJSON *asst_msg = cJSON_CreateObject(); cJSON_AddStringToObject(asst_msg, "role", "assistant"); @@ -295,7 +320,12 @@ static void agent_loop_task(void *arg) cJSON_Delete(messages); + MIMI_LOGI(TAG, "agent loop completed: iterations=%d final_text=%s", iteration, + (final_text && final_text[0] != '\0') ? "present" : "empty"); + if (final_text && final_text[0] != '\0') { + MIMI_LOGI(TAG, "sending final response (len=%u): %.512s%s", (unsigned)strlen(final_text), final_text, + strlen(final_text) > 512 ? "...[truncated]" : ""); OPERATE_RET save_user_rt = session_append(in_msg.chat_id, "user", user_text); OPERATE_RET save_asst_rt = session_append(in_msg.chat_id, "assistant", final_text); if (save_user_rt != OPRT_OK || save_asst_rt != OPRT_OK) { @@ -329,6 +359,7 @@ static void agent_loop_task(void *arg) free(final_text); free(in_msg.content); MIMI_LOGI(TAG, "free heap=%d", tal_system_get_free_heap_size()); + MIMI_LOGI(TAG, "================ AGENT TURN END ================"); } } diff --git a/apps/mimiclaw/agent/context_builder.c b/apps/mimiclaw/agent/context_builder.c index d18656508..f85a31351 100644 --- a/apps/mimiclaw/agent/context_builder.c +++ b/apps/mimiclaw/agent/context_builder.c @@ -4,7 +4,7 @@ #include "skills/skill_loader.h" #include "cJSON.h" -#include "tal_fs.h" +// #include "tal_fs.h" static const char *TAG = "context"; /* Reused scratch buffer for memory/skills blocks to keep stack usage low. */ @@ -12,10 +12,10 @@ static const char *TAG = "context"; static size_t append_file(char *buf, size_t size, size_t offset, const char *path, const char *header) { - TUYA_FILE f = tal_fopen(path, "r"); + TUYA_FILE f = mimi_fopen(path, "r"); if (!f || !buf || size == 0 || offset >= size - 1) { if (f) { - tal_fclose(f); + mimi_fclose(f); } return offset; } @@ -23,17 +23,17 @@ static size_t append_file(char *buf, size_t size, size_t offset, const char *pat if (header) { offset += snprintf(buf + offset, size - offset, "\n## %s\n\n", header); if (offset >= size - 1) { - tal_fclose(f); + mimi_fclose(f); return size - 1; } } - int n = tal_fread(buf + offset, (int)(size - offset - 1), f); + int n = mimi_fread(buf + offset, (int)(size - offset - 1), f); if (n > 0) { offset += (size_t)n; } buf[offset] = '\0'; - tal_fclose(f); + mimi_fclose(f); return offset; } @@ -55,7 +55,8 @@ OPERATE_RET context_build_system_prompt(char *buf, size_t size) "- web_search: Search the web for current information. " "Use this when you need up-to-date facts, news, weather, or anything beyond your training data.\n" "- get_current_time: Get the current date and time. " - "You do NOT have an internal clock - always use this tool when you need to know the time or date.\n" + "You do NOT have an internal clock - always use this tool when you need to know the time or date. " + "NEVER guess or reuse time from previous messages.\n" "- read_file: Read a file from SPIFFS (path must start with /spiffs/).\n" "- write_file: Write/overwrite a file on SPIFFS.\n" "- edit_file: Find-and-replace edit a file on SPIFFS.\n" @@ -66,6 +67,13 @@ OPERATE_RET context_build_system_prompt(char *buf, size_t size) "- cron_remove: Remove a scheduled cron job by ID.\n\n" "When using cron_add for Telegram delivery, always set channel='telegram' and a valid numeric chat_id.\n\n" "Use tools when needed. Provide your final answer as text after using tools.\n\n" + "## CRITICAL RULES\n" + "1. When the user asks about time, date, or anything time-dependent, you MUST call get_current_time. " + "The [Current time] in the user message is approximate - for precise answers, always use the tool.\n" + "2. When the user asks to set reminders or timed tasks, you MUST call cron_add. NEVER pretend a task was set " + "without actually calling the tool.\n" + "3. NEVER fabricate tool results. If you need information, call the appropriate tool.\n" + "4. When answering about weather, news, or real-time info, you MUST call web_search.\n\n" "## Memory\n" "You have persistent memory stored on local flash:\n" "- Long-term memory: /spiffs/memory/MEMORY.md\n" diff --git a/apps/mimiclaw/app_default.config b/apps/mimiclaw/app_default.config index c71144e94..12b4ca9b3 100755 --- a/apps/mimiclaw/app_default.config +++ b/apps/mimiclaw/app_default.config @@ -6,4 +6,4 @@ CONFIG_ENABLE_CUSTOM_CONFIG=y CONFIG_ENABLE_MBEDTLS_ECP_DP_SECP256R1_ENABLED=y CONFIG_ENABLE_MBEDTLS_KEY_EXCHANGE_ECDHE_PSK=y CONFIG_ENABLE_MBEDTLS_ECP_DP_SECP384R1_ENABLED=y -CONFIG_ENABLE_MBEDTLS_ECP_DP_CURVE25519_ENABLED=y +CONFIG_ENABLE_MBEDTLS_ECP_DP_CURVE25519_ENABLED=y \ No newline at end of file diff --git a/apps/mimiclaw/channels/feishu_bot.c b/apps/mimiclaw/channels/feishu_bot.c index 25ee1ee09..85885ca68 100644 --- a/apps/mimiclaw/channels/feishu_bot.c +++ b/apps/mimiclaw/channels/feishu_bot.c @@ -34,11 +34,13 @@ static THREAD_HANDLE s_ws_thread = NULL; #define FS_WS_RX_BUF_SIZE (64 * 1024) #define FS_WS_DEFAULT_RECONNECT_MS 5000 #define FS_WS_DEFAULT_PING_MS (120 * 1000) -#define FS_WS_FRAME_MAX_HEADERS 32 -#define FS_WS_FRAME_MAX_KEY 64 -#define FS_WS_FRAME_MAX_VALUE 256 -#define FS_DEDUP_CACHE_SIZE 512 -#define FS_MAX_FRAG_PARTS 8 +/* 收消息轮询等待(ms):越小收得越及时,过小会增加空转 CPU */ +#define FS_WS_POLL_WAIT_MS 150 +#define FS_WS_FRAME_MAX_HEADERS 32 +#define FS_WS_FRAME_MAX_KEY 64 +#define FS_WS_FRAME_MAX_VALUE 256 +#define FS_DEDUP_CACHE_SIZE 512 +#define FS_MAX_FRAG_PARTS 8 #ifndef MIMI_FS_POLL_STACK #define MIMI_FS_POLL_STACK (16 * 1024) #endif @@ -207,10 +209,12 @@ static void append_sender_id(char *sender_ids, size_t sender_ids_size, const cha } } -/* -------- dedup -------- */ +/* -------- dedup (event_id: 同一条推送重复送达; message_id: 同一条用户消息重复/重放) -------- */ -static uint64_t s_seen_msg_keys[FS_DEDUP_CACHE_SIZE] = {0}; -static size_t s_seen_msg_idx = 0; +static uint64_t s_seen_msg_keys[FS_DEDUP_CACHE_SIZE] = {0}; +static size_t s_seen_msg_idx = 0; +static uint64_t s_seen_event_keys[FS_DEDUP_CACHE_SIZE] = {0}; +static size_t s_seen_event_idx = 0; static bool seen_msg_contains(uint64_t key) { @@ -228,6 +232,22 @@ static void seen_msg_insert(uint64_t key) s_seen_msg_idx = (s_seen_msg_idx + 1) % FS_DEDUP_CACHE_SIZE; } +static bool seen_event_contains(uint64_t key) +{ + for (size_t i = 0; i < FS_DEDUP_CACHE_SIZE; i++) { + if (s_seen_event_keys[i] == key) { + return true; + } + } + return false; +} + +static void seen_event_insert(uint64_t key) +{ + s_seen_event_keys[s_seen_event_idx] = key; + s_seen_event_idx = (s_seen_event_idx + 1) % FS_DEDUP_CACHE_SIZE; +} + /* -------- TLS + HTTP -------- */ static OPERATE_RET ensure_fs_cert(const char *host) @@ -953,6 +973,24 @@ static int fs_conn_write(fs_ws_conn_t *conn, const uint8_t *data, int len) return sent; } +/* + * Layout-compatible prefix of the internal tuya_mbedtls_context_t (tuya_tls.c) + * so we can call mbedtls_ssl_get_bytes_avail() to detect data already + * decrypted but not yet consumed by the application. + */ +typedef struct { + tuya_tls_config_t _cfg; + mbedtls_ssl_context ssl_ctx; +} fs_tls_compat_t; + +static size_t fs_tls_bytes_avail(tuya_tls_hander tls) +{ + if (!tls) { + return 0; + } + return mbedtls_ssl_get_bytes_avail(&((fs_tls_compat_t *)tls)->ssl_ctx); +} + static int fs_conn_read(fs_ws_conn_t *conn, uint8_t *buf, int len, int timeout_ms) { if (!conn || !buf || len <= 0 || timeout_ms <= 0) { @@ -967,15 +1005,26 @@ static int fs_conn_read(fs_ws_conn_t *conn, uint8_t *buf, int len, int timeout_m return -1; } - TUYA_FD_SET_T readfds; - tal_net_fd_zero(&readfds); - tal_net_fd_set(conn->socket_fd, &readfds); - int ready = tal_net_select(conn->socket_fd + 1, &readfds, NULL, NULL, timeout_ms); - if (ready < 0) { - return -1; - } - if (ready == 0) { - return OPRT_RESOURCE_NOT_READY; + /* + * mbedtls decrypts a full TLS record at once but may return only part of + * it to the caller; the remainder stays in the SSL context's internal + * buffer. select() only monitors the raw TCP socket and is blind to + * that buffered plaintext, so it would block until the *next* TCP + * segment arrives — causing multi-second stalls. + * + * Fix: skip select() when mbedtls already has data ready. + */ + if (fs_tls_bytes_avail(conn->tls) == 0) { + TUYA_FD_SET_T readfds; + tal_net_fd_zero(&readfds); + tal_net_fd_set(conn->socket_fd, &readfds); + int ready = tal_net_select(conn->socket_fd + 1, &readfds, NULL, NULL, timeout_ms); + if (ready < 0) { + return -1; + } + if (ready == 0) { + return OPRT_RESOURCE_NOT_READY; + } } int n = tuya_tls_read(conn->tls, buf, (uint32_t)len); @@ -2148,7 +2197,10 @@ static void extract_message_text(const char *msg_type, const char *content_json, cJSON_Delete(obj); } -static OPERATE_RET fs_add_reaction(const char *message_id) +/* fs_add_reaction removed: synchronous HTTP in the WebSocket read loop + * blocks message reception. Re-add as async if needed. */ + +static OPERATE_RET __attribute__((unused)) fs_add_reaction(const char *message_id) { if (!message_id || message_id[0] == '\0') { return OPRT_INVALID_PARM; @@ -2209,11 +2261,14 @@ static void publish_inbound_feishu(const char *chat_id, const char *text) if (!in.content) { return; } + /* in.received_at_ms = tal_system_get_millisecond(); — mimi_msg_t 暂无此字段 */ OPERATE_RET rt = message_bus_push_inbound(&in); if (rt != OPRT_OK) { MIMI_LOGW(TAG, "push inbound failed rt=%d", rt); free(in.content); + } else { + MIMI_LOGI(TAG, "[feishu] latency +0ms push_inbound"); } } @@ -2226,6 +2281,7 @@ static void log_feishu_inbound_message(const char *chat_id, const char *sender_o const char *message_id, const char *msg_type, const char *chat_type, const char *text, const char *content_json) { + MIMI_LOGI(TAG, "[feishu] received msg chat_id=%s content=%s", log_str(chat_id), log_str(text)); MIMI_LOGI(TAG, "rx inbound_text channel=%s chat=%s event=%s type=%s len=%u", MIMI_CHAN_FEISHU, log_str(chat_id), log_str(event_type), log_str(msg_type), (unsigned)strlen(log_str(text))); (void)sender_open_id; @@ -2242,27 +2298,49 @@ static void handle_event_payload(const uint8_t *payload, size_t payload_len) cJSON *root = cJSON_ParseWithLength((const char *)payload, payload_len); if (!root) { + MIMI_LOGW(TAG, "[feishu] event payload parse failed len=%u", (unsigned)payload_len); return; } + // debug, output root as json string + char *json_str = cJSON_PrintUnformatted(root); + MIMI_LOGI(TAG, "[feishu] event payload: %s", json_str); + cJSON_free(json_str); + const char *event_type = NULL; + const char *event_id = NULL; cJSON *header = cJSON_GetObjectItem(root, "header"); if (cJSON_IsObject(header)) { event_type = json_str2(header, "event_type", NULL); + event_id = json_str2(header, "event_id", NULL); } if (!event_type) { event_type = json_str2(root, "type", NULL); } + MIMI_LOGI(TAG, "[feishu] event received event_type=%s", event_type ? event_type : "(null)"); + if (!event_type || strcmp(event_type, "im.message.receive_v1") != 0) { + MIMI_LOGI(TAG, "[feishu] skip event (only handle im.message.receive_v1)"); cJSON_Delete(root); return; } + /* 同一条推送重复送达(如重连后重放):按 event_id 去重 */ + if (event_id && event_id[0] != '\0') { + uint64_t ev_key = fnv1a64(event_id); + if (seen_event_contains(ev_key)) { + MIMI_LOGI(TAG, "[feishu] duplicate event_id dropped event_id=%s", event_id); + cJSON_Delete(root); + return; + } + } + cJSON *event = cJSON_GetObjectItem(root, "event"); cJSON *sender = event ? cJSON_GetObjectItem(event, "sender") : NULL; cJSON *message = event ? cJSON_GetObjectItem(event, "message") : NULL; if (!cJSON_IsObject(sender) || !cJSON_IsObject(message)) { + MIMI_LOGW(TAG, "[feishu] event missing sender or message object"); cJSON_Delete(root); return; } @@ -2305,15 +2383,23 @@ static void handle_event_payload(const uint8_t *payload, size_t payload_len) return; } + /* 同一条用户消息重复/重放:按 message_id 去重 */ const char *message_id = json_str2(message, "message_id", NULL); if (message_id && message_id[0]) { uint64_t msg_key = fnv1a64(message_id); if (seen_msg_contains(msg_key)) { + MIMI_LOGI(TAG, "[feishu] duplicate message_id dropped message_id=%s", message_id); + if (event_id && event_id[0] != '\0') { + seen_event_insert(fnv1a64(event_id)); + } cJSON_Delete(root); return; } seen_msg_insert(msg_key); } + if (event_id && event_id[0] != '\0') { + seen_event_insert(fnv1a64(event_id)); + } const char *chat_id = json_str2(message, "chat_id", NULL); const char *chat_type = json_str2(message, "chat_type", NULL); @@ -2323,6 +2409,8 @@ static void handle_event_payload(const uint8_t *payload, size_t payload_len) char text[2048] = {0}; extract_message_text(msg_type ? msg_type : "unknown", content_json, text, sizeof(text)); if (text[0] == '\0') { + MIMI_LOGW(TAG, "[feishu] message text empty msg_type=%s (unsupported or empty content)", + msg_type ? msg_type : "null"); cJSON_Delete(root); return; } @@ -2344,9 +2432,8 @@ static void handle_event_payload(const uint8_t *payload, size_t payload_len) publish_inbound_feishu(reply_to, text); - if (message_id && message_id[0] != '\0') { - (void)fs_add_reaction(message_id); - } + /* 不在收消息路径里同步调 reaction:fs_add_reaction 会发 HTTP,阻塞 WebSocket + * 读循环,导致下一条消息很久才被读到。需要 reaction 时可改为异步/独立线程。 */ cJSON_Delete(root); } @@ -2423,18 +2510,13 @@ static OPERATE_RET handle_data_pb_frame(fs_ws_conn_t *conn, const fs_pb_frame_t return rt; } - if (type && strcmp(type, "event") == 0 && payload && payload_len > 0) { - handle_event_payload(payload, payload_len); - } - - free(payload); - + /* 飞书要求 3 秒内确认,否则会重推。先回 ACK 再处理业务,避免 handle_event_payload 内 HTTP 等导致超时。 */ static const char ack_ok[] = "{\"code\":200}"; fs_pb_frame_t *ack = fs_pb_frame_new(); if (!ack) { + free(payload); return OPRT_MALLOC_FAILED; } - ack->seq_id = frame->seq_id; ack->log_id = frame->log_id; ack->service = frame->service; @@ -2449,12 +2531,25 @@ static OPERATE_RET handle_data_pb_frame(fs_ws_conn_t *conn, const fs_pb_frame_t } ack->payload = (uint8_t *)ack_ok; ack->payload_len = sizeof(ack_ok) - 1; - rt = send_pb_frame(conn, ack); ack->payload = NULL; ack->payload_len = 0; fs_pb_frame_delete(ack); - return rt; + if (rt != OPRT_OK) { + free(payload); + return rt; + } + + if (payload && payload_len > 0) { + if (type && strcmp(type, "event") == 0) { + handle_event_payload(payload, payload_len); + } else { + MIMI_LOGI(TAG, "[feishu] ws data frame type=%s (only type=event handled for messages)", + type ? type : "(null)"); + } + } + free(payload); + return OPRT_OK; } /* -------- ws main loop -------- */ @@ -2538,7 +2633,7 @@ static void feishu_ws_task(void *arg) uint8_t opcode = 0; uint8_t *payload = NULL; size_t payload_len = 0; - rt = fs_ws_poll_frame(conn, 500, &opcode, &payload, &payload_len); + rt = fs_ws_poll_frame(conn, FS_WS_POLL_WAIT_MS, &opcode, &payload, &payload_len); if (rt == OPRT_RESOURCE_NOT_READY) { continue; } diff --git a/apps/mimiclaw/cli/serial_cli.c b/apps/mimiclaw/cli/serial_cli.c index f7c749b91..8c2d7edf2 100644 --- a/apps/mimiclaw/cli/serial_cli.c +++ b/apps/mimiclaw/cli/serial_cli.c @@ -8,7 +8,7 @@ #include "channels/discord_bot.h" #include "channels/feishu_bot.h" #include "tal_cli.h" -#include "tal_fs.h" +// #include "tal_fs.h" #include "channels/telegram_bot.h" #include "tools/tool_web_search.h" #include "wifi/wifi_manager.h" @@ -689,15 +689,15 @@ static void cmd_file_read(int argc, char *argv[]) } memset(buf, 0, 4096); - TUYA_FILE f = tal_fopen(path, "r"); + TUYA_FILE f = mimi_fopen(path, "r"); if (!f) { cli_echof("file_read open failed: %s", path); tal_free(buf); return; } - int n = tal_fread(buf, 4095, f); - tal_fclose(f); + int n = mimi_fread(buf, 4095, f); + mimi_fclose(f); if (n < 0) { cli_echof("file_read read failed: %s", path); tal_free(buf); @@ -705,7 +705,7 @@ static void cmd_file_read(int argc, char *argv[]) } buf[n] = '\0'; - int file_size = tal_fgetsize(path); + int file_size = mimi_fgetsize(path); if (buf[0] == '\0') { cli_echof("file_read empty: %s", path); tal_free(buf); @@ -777,7 +777,7 @@ static void cli_session_list_cb(const char *name, void *user_data) snprintf(path, sizeof(path), "%s/%s", base_dir, name); } - int size = path[0] ? tal_fgetsize(path) : -1; + int size = path[0] ? mimi_fgetsize(path) : -1; if (size >= 0) { cli_echof("session[%u]: %s (%d B)", (unsigned)idx, name ? name : "", size); } else { @@ -791,7 +791,7 @@ static void cli_list_spiffs_entries(void) uint32_t file_count = 0; TUYA_DIR root = NULL; - if (tal_dir_open(MIMI_SPIFFS_BASE, &root) != OPRT_OK || !root) { + if (mimi_dir_open(MIMI_SPIFFS_BASE, &root) != OPRT_OK || !root) { cli_echof("/spiffs open failed"); return; } @@ -799,12 +799,12 @@ static void cli_list_spiffs_entries(void) cli_echof("/spiffs all listing:"); while (1) { TUYA_FILEINFO info = NULL; - if (tal_dir_read(root, &info) != OPRT_OK || !info) { + if (mimi_dir_read(root, &info) != OPRT_OK || !info) { break; } const char *name = NULL; - if (tal_dir_name(info, &name) != OPRT_OK || !name || name[0] == '\0') { + if (mimi_dir_name(info, &name) != OPRT_OK || !name || name[0] == '\0') { continue; } if (strcmp(name, ".") == 0 || strcmp(name, "..") == 0) { @@ -815,8 +815,8 @@ static void cli_list_spiffs_entries(void) snprintf(path, sizeof(path), "%s/%s", MIMI_SPIFFS_BASE, name); BOOL_T is_regular = FALSE; - if (tal_dir_is_regular(info, &is_regular) == OPRT_OK && is_regular) { - int size = tal_fgetsize(path); + if (mimi_dir_is_regular(info, &is_regular) == OPRT_OK && is_regular) { + int size = mimi_fgetsize(path); cli_echof("/spiffs[%u]: %s (%d B)", (unsigned)entry_idx++, path, size); file_count++; continue; @@ -825,18 +825,18 @@ static void cli_list_spiffs_entries(void) cli_echof("/spiffs[%u]: %s/ ()", (unsigned)entry_idx++, path); TUYA_DIR sub = NULL; - if (tal_dir_open(path, &sub) != OPRT_OK || !sub) { + if (mimi_dir_open(path, &sub) != OPRT_OK || !sub) { continue; } while (1) { TUYA_FILEINFO sub_info = NULL; - if (tal_dir_read(sub, &sub_info) != OPRT_OK || !sub_info) { + if (mimi_dir_read(sub, &sub_info) != OPRT_OK || !sub_info) { break; } const char *sub_name = NULL; - if (tal_dir_name(sub_info, &sub_name) != OPRT_OK || !sub_name || sub_name[0] == '\0') { + if (mimi_dir_name(sub_info, &sub_name) != OPRT_OK || !sub_name || sub_name[0] == '\0') { continue; } if (strcmp(sub_name, ".") == 0 || strcmp(sub_name, "..") == 0) { @@ -847,8 +847,8 @@ static void cli_list_spiffs_entries(void) snprintf(sub_path, sizeof(sub_path), "%s/%s", path, sub_name); BOOL_T sub_is_regular = FALSE; - if (tal_dir_is_regular(sub_info, &sub_is_regular) == OPRT_OK && sub_is_regular) { - int sub_size = tal_fgetsize(sub_path); + if (mimi_dir_is_regular(sub_info, &sub_is_regular) == OPRT_OK && sub_is_regular) { + int sub_size = mimi_fgetsize(sub_path); cli_echof("/spiffs[%u]: %s (%d B)", (unsigned)entry_idx++, sub_path, sub_size); file_count++; } else { @@ -856,10 +856,10 @@ static void cli_list_spiffs_entries(void) } } - tal_dir_close(sub); + mimi_dir_close(sub); } - tal_dir_close(root); + mimi_dir_close(root); cli_echof("file_list done files=%u entries=%u", (unsigned)file_count, (unsigned)entry_idx); } @@ -912,7 +912,7 @@ static void cmd_file_clear(int argc, char *argv[]) return; } - OPERATE_RET rt = tal_fs_remove(path); + OPERATE_RET rt = mimi_fs_remove(path); cli_echof("file_clear rt=%d", rt); } diff --git a/apps/mimiclaw/cron/cron_service.c b/apps/mimiclaw/cron/cron_service.c index 50942570f..bc2f647c9 100644 --- a/apps/mimiclaw/cron/cron_service.c +++ b/apps/mimiclaw/cron/cron_service.c @@ -4,7 +4,7 @@ #include "mimi_config.h" #include "cJSON.h" -#include "tal_fs.h" +// #include "tal_fs.h" #include @@ -62,29 +62,29 @@ static void cron_generate_id(char *id_buf, size_t id_buf_size) static OPERATE_RET cron_load_jobs(void) { - TUYA_FILE f = tal_fopen(MIMI_CRON_FILE, "r"); + TUYA_FILE f = mimi_fopen(MIMI_CRON_FILE, "r"); if (!f) { MIMI_LOGI(TAG, "no cron file found, starting fresh"); s_job_count = 0; return OPRT_OK; } - int fsize = tal_fgetsize(MIMI_CRON_FILE); + int fsize = mimi_fgetsize(MIMI_CRON_FILE); if (fsize <= 0 || fsize > CRON_FILE_MAX_BYTES) { MIMI_LOGW(TAG, "cron file invalid size: %d", fsize); - tal_fclose(f); + mimi_fclose(f); s_job_count = 0; return OPRT_OK; } char *buf = (char *)malloc((size_t)fsize + 1); if (!buf) { - tal_fclose(f); + mimi_fclose(f); return OPRT_MALLOC_FAILED; } - int n = tal_fread(buf, fsize, f); - tal_fclose(f); + int n = mimi_fread(buf, fsize, f); + mimi_fclose(f); if (n < 0) { free(buf); return OPRT_COM_ERROR; @@ -221,7 +221,7 @@ static OPERATE_RET cron_save_jobs(void) return OPRT_MALLOC_FAILED; } - TUYA_FILE f = tal_fopen(MIMI_CRON_FILE, "w"); + TUYA_FILE f = mimi_fopen(MIMI_CRON_FILE, "w"); if (!f) { cJSON_free(json_str); MIMI_LOGE(TAG, "failed to open %s for writing", MIMI_CRON_FILE); @@ -229,8 +229,8 @@ static OPERATE_RET cron_save_jobs(void) } size_t len = strlen(json_str); - int wn = tal_fwrite(json_str, (int)len, f); - tal_fclose(f); + int wn = mimi_fwrite(json_str, (int)len, f); + mimi_fclose(f); cJSON_free(json_str); if (wn < 0 || (size_t)wn != len) { @@ -244,8 +244,9 @@ static OPERATE_RET cron_save_jobs(void) static void cron_process_due_jobs(void) { - int64_t now = cron_now_epoch(); - bool changed = false; + int64_t now = cron_now_epoch(); + MIMI_LOGI(TAG, "checking cron jobs at epoch %lld, job count=%d", (long long)now, s_job_count); + bool changed = false; for (int i = 0; i < s_job_count; i++) { cron_job_t *job = &s_jobs[i]; diff --git a/apps/mimiclaw/heartbeat/heartbeat.c b/apps/mimiclaw/heartbeat/heartbeat.c index a24851659..7183cd206 100644 --- a/apps/mimiclaw/heartbeat/heartbeat.c +++ b/apps/mimiclaw/heartbeat/heartbeat.c @@ -3,7 +3,7 @@ #include "bus/message_bus.h" #include "mimi_config.h" -#include "tal_fs.h" +// #include "tal_fs.h" #include #include @@ -18,7 +18,7 @@ static TIMER_ID s_heartbeat_timer = NULL; static bool heartbeat_has_tasks(void) { - TUYA_FILE f = tal_fopen(MIMI_HEARTBEAT_FILE, "r"); + TUYA_FILE f = mimi_fopen(MIMI_HEARTBEAT_FILE, "r"); if (!f) { return false; } @@ -26,7 +26,7 @@ static bool heartbeat_has_tasks(void) char line[256] = {0}; bool found_task = false; - while (tal_fgets(line, sizeof(line), f)) { + while (mimi_fgets(line, sizeof(line), f)) { const char *p = line; while (*p && isspace((unsigned char)*p)) { p++; @@ -51,7 +51,7 @@ static bool heartbeat_has_tasks(void) break; } - tal_fclose(f); + mimi_fclose(f); return found_task; } diff --git a/apps/mimiclaw/llm/llm_proxy.c b/apps/mimiclaw/llm/llm_proxy.c index bfe72fc1f..1c0d43dbd 100644 --- a/apps/mimiclaw/llm/llm_proxy.c +++ b/apps/mimiclaw/llm/llm_proxy.c @@ -27,10 +27,23 @@ typedef struct { static llm_endpoint_t s_openai_endpoint = {0}; static llm_endpoint_t s_anthropic_endpoint = {0}; +#define LLM_LOG_PREVIEW_LEN 1024 + static void llm_log_payload(const char *label, const char *payload) { size_t total = payload ? strlen(payload) : 0; - MIMI_LOGI(TAG, "%s (%u bytes)", label ? label : "llm payload", (unsigned)total); + MIMI_LOGI(TAG, "========== %s (%u bytes) ==========", label ? label : "llm payload", (unsigned)total); + if (payload && total > 0) { + if (total <= LLM_LOG_PREVIEW_LEN) { + MIMI_LOGI(TAG, "%s", payload); + } else { + char preview[LLM_LOG_PREVIEW_LEN + 1]; + memcpy(preview, payload, LLM_LOG_PREVIEW_LEN); + preview[LLM_LOG_PREVIEW_LEN] = '\0'; + MIMI_LOGI(TAG, "%s ...[truncated, %u more bytes]", preview, (unsigned)(total - LLM_LOG_PREVIEW_LEN)); + } + } + MIMI_LOGI(TAG, "========== end %s ==========", label ? label : "llm payload"); } static void safe_copy(char *dst, size_t dst_size, const char *src) @@ -712,10 +725,12 @@ OPERATE_RET llm_chat(const char *system_prompt, const char *messages_json, char if (!raw_resp) { return OPRT_MALLOC_FAILED; } + MIMI_LOGI(TAG, ">>> LLM chat request: provider=%s model=%s", s_provider, s_model); llm_log_payload("LLM request", post_data); uint16_t status = 0; OPERATE_RET rt = llm_http_call(post_data, raw_resp, MIMI_LLM_STREAM_BUF_SIZE, &status); cJSON_free(post_data); + MIMI_LOGI(TAG, "<<< LLM chat response: http_status=%u rt=%d", (unsigned)status, rt); llm_log_payload("LLM raw response", raw_resp); if (rt != OPRT_OK) { @@ -772,6 +787,12 @@ void llm_response_free(llm_response_t *resp) } OPERATE_RET llm_chat_tools(const char *system_prompt, cJSON *messages, const char *tools_json, llm_response_t *resp) +{ + return llm_chat_tools_ex(system_prompt, messages, tools_json, false, resp); +} + +OPERATE_RET llm_chat_tools_ex(const char *system_prompt, cJSON *messages, const char *tools_json, bool force_tool, + llm_response_t *resp) { if (!resp) { return OPRT_INVALID_PARM; @@ -806,7 +827,7 @@ OPERATE_RET llm_chat_tools(const char *system_prompt, cJSON *messages, const cha cJSON *tools = convert_tools_openai(tools_json); if (tools) { cJSON_AddItemToObject(body, "tools", tools); - cJSON_AddStringToObject(body, "tool_choice", "auto"); + cJSON_AddStringToObject(body, "tool_choice", force_tool ? "required" : "auto"); } } } else { @@ -836,17 +857,22 @@ OPERATE_RET llm_chat_tools(const char *system_prompt, cJSON *messages, const cha cJSON_free(post_data); return OPRT_MALLOC_FAILED; } + MIMI_LOGI(TAG, ">>> LLM tools request: provider=%s model=%s host=%s", s_provider, s_model, + llm_api_host() ? llm_api_host() : "(null)"); llm_log_payload("LLM tools request", post_data); uint16_t status = 0; OPERATE_RET rt = llm_http_call(post_data, raw_resp, MIMI_LLM_STREAM_BUF_SIZE, &status); cJSON_free(post_data); + MIMI_LOGI(TAG, "<<< LLM tools response: http_status=%u rt=%d", (unsigned)status, rt); llm_log_payload("LLM tools raw response", raw_resp); if (rt != OPRT_OK) { + MIMI_LOGE(TAG, "LLM tools HTTP call failed rt=%d", rt); free(raw_resp); return rt; } if (status != 200) { + MIMI_LOGE(TAG, "LLM tools API error HTTP %u, body: %.512s", (unsigned)status, raw_resp); free(raw_resp); return OPRT_COM_ERROR; } @@ -950,6 +976,21 @@ OPERATE_RET llm_chat_tools(const char *system_prompt, cJSON *messages, const cha } } + /* Log parsed LLM response details */ + MIMI_LOGI(TAG, "LLM parsed result: tool_use=%s text_len=%u call_count=%d", resp->tool_use ? "true" : "false", + (unsigned)resp->text_len, resp->call_count); + if (resp->text && resp->text_len > 0) { + if (resp->text_len <= 512) { + MIMI_LOGI(TAG, "LLM response text: %s", resp->text); + } else { + MIMI_LOGI(TAG, "LLM response text (first 512): %.512s ...[truncated]", resp->text); + } + } + for (int log_i = 0; log_i < resp->call_count; log_i++) { + MIMI_LOGI(TAG, "LLM tool_call[%d]: id=%s name=%s input=%s", log_i, resp->calls[log_i].id, + resp->calls[log_i].name, resp->calls[log_i].input ? resp->calls[log_i].input : "(null)"); + } + cJSON_Delete(root); return OPRT_OK; } diff --git a/apps/mimiclaw/llm/llm_proxy.h b/apps/mimiclaw/llm/llm_proxy.h index 14543b1a0..d5c854cb1 100644 --- a/apps/mimiclaw/llm/llm_proxy.h +++ b/apps/mimiclaw/llm/llm_proxy.h @@ -27,4 +27,6 @@ OPERATE_RET llm_set_provider(const char *provider); OPERATE_RET llm_set_model(const char *model); OPERATE_RET llm_chat(const char *system_prompt, const char *messages_json, char *response_buf, size_t buf_size); OPERATE_RET llm_chat_tools(const char *system_prompt, cJSON *messages, const char *tools_json, llm_response_t *resp); +OPERATE_RET llm_chat_tools_ex(const char *system_prompt, cJSON *messages, const char *tools_json, bool force_tool, + llm_response_t *resp); void llm_response_free(llm_response_t *resp); diff --git a/apps/mimiclaw/memory/memory_store.c b/apps/mimiclaw/memory/memory_store.c index 72e1a8a65..f6b64aba3 100644 --- a/apps/mimiclaw/memory/memory_store.c +++ b/apps/mimiclaw/memory/memory_store.c @@ -1,7 +1,7 @@ #include "memory_store.h" #include "mimi_config.h" -#include "tal_fs.h" +// #include "tal_fs.h" static const char *TAG = "memory"; @@ -42,20 +42,20 @@ OPERATE_RET memory_read_long_term(char *buf, size_t size) return OPRT_INVALID_PARM; } - TUYA_FILE f = tal_fopen(MIMI_MEMORY_FILE, "r"); + TUYA_FILE f = mimi_fopen(MIMI_MEMORY_FILE, "r"); if (!f) { buf[0] = '\0'; return OPRT_NOT_FOUND; } - int n = tal_fread(buf, (int)(size - 1), f); + int n = mimi_fread(buf, (int)(size - 1), f); if (n < 0) { buf[0] = '\0'; - tal_fclose(f); + mimi_fclose(f); return OPRT_COM_ERROR; } buf[n] = '\0'; - tal_fclose(f); + mimi_fclose(f); return OPRT_OK; } @@ -65,13 +65,13 @@ OPERATE_RET memory_write_long_term(const char *content) return OPRT_INVALID_PARM; } - TUYA_FILE f = tal_fopen(MIMI_MEMORY_FILE, "w"); + TUYA_FILE f = mimi_fopen(MIMI_MEMORY_FILE, "w"); if (!f) { return OPRT_FILE_OPEN_FAILED; } - int n = tal_fwrite((void *)content, (int)strlen(content), f); - tal_fclose(f); + int n = mimi_fwrite((void *)content, (int)strlen(content), f); + mimi_fclose(f); if (n < 0) { return OPRT_COM_ERROR; } @@ -89,22 +89,22 @@ OPERATE_RET memory_append_today(const char *note) get_date_str(date_str, sizeof(date_str), 0); snprintf(path, sizeof(path), "%s/%s.md", MIMI_SPIFFS_MEMORY_DIR, date_str); - TUYA_FILE f = tal_fopen(path, "a"); + TUYA_FILE f = mimi_fopen(path, "a"); if (!f) { - f = tal_fopen(path, "w"); + f = mimi_fopen(path, "w"); if (!f) { return OPRT_FILE_OPEN_FAILED; } char hdr[64] = {0}; int hn = snprintf(hdr, sizeof(hdr), "# %s\n\n", date_str); if (hn > 0) { - (void)tal_fwrite(hdr, hn, f); + (void)mimi_fwrite(hdr, hn, f); } } - (void)tal_fwrite((void *)note, (int)strlen(note), f); - (void)tal_fwrite("\n", 1, f); - tal_fclose(f); + (void)mimi_fwrite((void *)note, (int)strlen(note), f); + (void)mimi_fwrite("\n", 1, f); + mimi_fclose(f); return OPRT_OK; } @@ -124,7 +124,7 @@ OPERATE_RET memory_read_recent(char *buf, size_t size, int days) get_date_str(date_str, sizeof(date_str), i); snprintf(path, sizeof(path), "%s/%s.md", MIMI_SPIFFS_MEMORY_DIR, date_str); - TUYA_FILE f = tal_fopen(path, "r"); + TUYA_FILE f = mimi_fopen(path, "r"); if (!f) { continue; } @@ -133,12 +133,12 @@ OPERATE_RET memory_read_recent(char *buf, size_t size, int days) off += snprintf(buf + off, size - off, "\n---\n"); } - int n = tal_fread(buf + off, (int)(size - off - 1), f); + int n = mimi_fread(buf + off, (int)(size - off - 1), f); if (n > 0) { off += (size_t)n; } buf[off] = '\0'; - tal_fclose(f); + mimi_fclose(f); } return OPRT_OK; diff --git a/apps/mimiclaw/memory/session_mgr.c b/apps/mimiclaw/memory/session_mgr.c index 863e6f42f..92cac42a1 100644 --- a/apps/mimiclaw/memory/session_mgr.c +++ b/apps/mimiclaw/memory/session_mgr.c @@ -2,7 +2,7 @@ #include "mimi_config.h" #include "cJSON.h" -#include "tal_fs.h" +// #include "tal_fs.h" static const char *TAG = "session"; @@ -52,14 +52,14 @@ OPERATE_RET session_append(const char *chat_id, const char *role, const char *co char path[128] = {0}; session_path(chat_id, path, sizeof(path)); - TUYA_FILE f = tal_fopen(path, "a"); + TUYA_FILE f = mimi_fopen(path, "a"); if (!f) { return OPRT_FILE_OPEN_FAILED; } cJSON *obj = cJSON_CreateObject(); if (!obj) { - tal_fclose(f); + mimi_fclose(f); return OPRT_CR_CJSON_ERR; } @@ -71,14 +71,14 @@ OPERATE_RET session_append(const char *chat_id, const char *role, const char *co cJSON_Delete(obj); if (!line) { - tal_fclose(f); + mimi_fclose(f); return OPRT_CR_CJSON_ERR; } - (void)tal_fwrite(line, (int)strlen(line), f); - (void)tal_fwrite("\n", 1, f); + (void)mimi_fwrite(line, (int)strlen(line), f); + (void)mimi_fwrite("\n", 1, f); cJSON_free(line); - tal_fclose(f); + mimi_fclose(f); return OPRT_OK; } @@ -94,7 +94,7 @@ OPERATE_RET session_get_history_json(const char *chat_id, char *buf, size_t size char path[128] = {0}; session_path(chat_id, path, sizeof(path)); - TUYA_FILE f = tal_fopen(path, "r"); + TUYA_FILE f = mimi_fopen(path, "r"); if (!f) { snprintf(buf, size, "[]"); return OPRT_OK; @@ -105,7 +105,7 @@ OPERATE_RET session_get_history_json(const char *chat_id, char *buf, size_t size int write_idx = 0; char line[2048] = {0}; - while (tal_fgets(line, (int)sizeof(line), f)) { + while (mimi_fgets(line, (int)sizeof(line), f)) { size_t len = strlen(line); if (len > 0 && line[len - 1] == '\n') { line[len - 1] = '\0'; @@ -128,7 +128,7 @@ OPERATE_RET session_get_history_json(const char *chat_id, char *buf, size_t size count++; } } - tal_fclose(f); + mimi_fclose(f); cJSON *arr = cJSON_CreateArray(); int start = (count < max_msgs) ? 0 : write_idx; @@ -182,7 +182,7 @@ OPERATE_RET session_clear(const char *chat_id) char path[128] = {0}; session_path(normalized, path, sizeof(path)); - return (tal_fs_remove(path) == OPRT_OK) ? OPRT_OK : OPRT_NOT_FOUND; + return (mimi_fs_remove(path) == OPRT_OK) ? OPRT_OK : OPRT_NOT_FOUND; } OPERATE_RET session_clear_all(uint32_t *out_removed) @@ -193,18 +193,18 @@ OPERATE_RET session_clear_all(uint32_t *out_removed) } TUYA_DIR dir = NULL; - if (tal_dir_open(MIMI_SPIFFS_SESSION_DIR, &dir) != OPRT_OK || !dir) { + if (mimi_dir_open(MIMI_SPIFFS_SESSION_DIR, &dir) != OPRT_OK || !dir) { return OPRT_FILE_OPEN_FAILED; } while (1) { TUYA_FILEINFO info = NULL; - if (tal_dir_read(dir, &info) != OPRT_OK || !info) { + if (mimi_dir_read(dir, &info) != OPRT_OK || !info) { break; } const char *name = NULL; - if (tal_dir_name(info, &name) != OPRT_OK || !name || name[0] == '\0') { + if (mimi_dir_name(info, &name) != OPRT_OK || !name || name[0] == '\0') { continue; } if (strcmp(name, ".") == 0 || strcmp(name, "..") == 0) { @@ -212,18 +212,18 @@ OPERATE_RET session_clear_all(uint32_t *out_removed) } BOOL_T is_regular = FALSE; - if (tal_dir_is_regular(info, &is_regular) != OPRT_OK || !is_regular) { + if (mimi_dir_is_regular(info, &is_regular) != OPRT_OK || !is_regular) { continue; } char path[160] = {0}; snprintf(path, sizeof(path), "%s/%s", MIMI_SPIFFS_SESSION_DIR, name); - if (tal_fs_remove(path) == OPRT_OK) { + if (mimi_fs_remove(path) == OPRT_OK) { removed++; } } - tal_dir_close(dir); + mimi_dir_close(dir); if (out_removed) { *out_removed = removed; } @@ -238,19 +238,19 @@ OPERATE_RET session_list(session_list_cb_t cb, void *user_data, uint32_t *out_co } TUYA_DIR dir = NULL; - if (tal_dir_open(MIMI_SPIFFS_SESSION_DIR, &dir) != OPRT_OK || !dir) { + if (mimi_dir_open(MIMI_SPIFFS_SESSION_DIR, &dir) != OPRT_OK || !dir) { MIMI_LOGW(TAG, "open session dir failed"); return OPRT_FILE_OPEN_FAILED; } while (1) { TUYA_FILEINFO info = NULL; - if (tal_dir_read(dir, &info) != OPRT_OK || !info) { + if (mimi_dir_read(dir, &info) != OPRT_OK || !info) { break; } const char *name = NULL; - if (tal_dir_name(info, &name) == OPRT_OK && name && name[0] != '\0') { + if (mimi_dir_name(info, &name) == OPRT_OK && name && name[0] != '\0') { if (strcmp(name, ".") == 0 || strcmp(name, "..") == 0) { continue; } @@ -262,7 +262,7 @@ OPERATE_RET session_list(session_list_cb_t cb, void *user_data, uint32_t *out_co } } - tal_dir_close(dir); + mimi_dir_close(dir); if (out_count) { *out_count = count; } diff --git a/apps/mimiclaw/mimi.c b/apps/mimiclaw/mimi.c index 879f7f510..5d316dfd3 100644 --- a/apps/mimiclaw/mimi.c +++ b/apps/mimiclaw/mimi.c @@ -15,6 +15,7 @@ #include "proxy/http_proxy.h" #include "skills/skill_loader.h" #include "channels/telegram_bot.h" +#include "tools/tool_get_time.h" #include "tools/tool_registry.h" #include "tuya_register_center.h" #include "tuya_tls.h" @@ -22,7 +23,8 @@ #include "cJSON.h" #include "netmgr.h" -#include "tal_fs.h" +// #include "tal_fs.h" +#include "tkl_fs.h" #include "tkl_output.h" #include @@ -128,6 +130,36 @@ static void mimi_runtime_init(void) .key = "dflfuap134ddlduq", }); +#if MIMI_USE_SDCARD + MIMI_LOGI(TAG, "Mounting SD card to %s...", MIMI_FS_BASE); + int retry = 0; + OPERATE_RET rt = OPRT_OK; + while (retry < 3) { + rt = tkl_fs_mount(MIMI_FS_BASE, DEV_SDCARD); + if (rt == OPRT_OK) { + MIMI_LOGI(TAG, "SD card mount success"); + break; + } + MIMI_LOGW(TAG, "SD card mount failed (rt=%d), retry %d/3...", rt, retry + 1); + tal_system_sleep(1000); + retry++; + } +#elif ENABLE_FILE_SYSTEM + MIMI_LOGI(TAG, "Mounting inner flash to %s...", MIMI_FS_BASE); + int retry = 0; + OPERATE_RET rt = OPRT_OK; + while (retry < 3) { + rt = tkl_fs_mount(MIMI_FS_BASE, DEV_INNER_FLASH); + if (rt == OPRT_OK) { + MIMI_LOGI(TAG, "Inner flash mount success"); + break; + } + MIMI_LOGW(TAG, "Inner flash mount failed (rt=%d), retry %d/3...", rt, retry + 1); + tal_system_sleep(1000); + retry++; + } +#endif + (void)tal_sw_timer_init(); (void)tal_workq_init(); (void)tuya_tls_init(); @@ -138,12 +170,19 @@ static void mimi_runtime_init(void) static OPERATE_RET ensure_dir(const char *path) { - BOOL_T exists = FALSE; - if (tal_fs_is_exist(path, &exists) == OPRT_OK && exists) { + // Skip mkdir for mount point root itself + if (strcmp(path, "/sdcard") == 0 || strcmp(path, "/spiffs") == 0) { return OPRT_OK; } - OPERATE_RET rt = tal_fs_mkdir(path); + BOOL_T exists = FALSE; + OPERATE_RET rt = mimi_fs_is_exist(path, &exists); + if (rt == OPRT_OK && exists) { + return OPRT_OK; + } + + MIMI_LOGI(TAG, "Creating directory: %s", path); + rt = mimi_fs_mkdir(path); if (rt != OPRT_OK) { MIMI_LOGE(TAG, "mkdir failed: %s rt=%d", path, rt); return rt; @@ -154,31 +193,43 @@ static OPERATE_RET ensure_dir(const char *path) static OPERATE_RET init_storage(void) { - OPERATE_RET rt = ensure_dir(MIMI_SPIFFS_BASE); +#if MIMI_USE_SDCARD + BOOL_T mounted = FALSE; + if (mimi_fs_is_exist(MIMI_FS_BASE, &mounted) != OPRT_OK || !mounted) { + MIMI_LOGW(TAG, "Storage root %s stat failed (SD mount may still be valid), creating subdirs", MIMI_FS_BASE); + } +#endif + + OPERATE_RET rt = ensure_dir(MIMI_FS_BASE); if (rt != OPRT_OK) { return rt; } rt = ensure_dir(MIMI_SPIFFS_CONFIG_DIR); if (rt != OPRT_OK) { + MIMI_LOGE(TAG, "Config dir init failed: %d", rt); return rt; } rt = ensure_dir(MIMI_SPIFFS_MEMORY_DIR); if (rt != OPRT_OK) { + MIMI_LOGE(TAG, "Memory dir init failed: %d", rt); return rt; } rt = ensure_dir(MIMI_SPIFFS_SESSION_DIR); if (rt != OPRT_OK) { + MIMI_LOGE(TAG, "Session dir init failed: %d", rt); return rt; } rt = ensure_dir(MIMI_SPIFFS_SKILLS_DIR); if (rt != OPRT_OK) { + MIMI_LOGE(TAG, "Skills dir init failed: %d", rt); return rt; } + MIMI_LOGI(TAG, "Storage initialized on %s", MIMI_FS_BASE); return OPRT_OK; } @@ -351,6 +402,7 @@ void mimi_app_main(void) (void)feishu_bot_init(); (void)llm_proxy_init(); (void)tool_registry_init(); + tool_get_time_init(); /* set timezone early so log timestamps are local */ (void)cron_service_init(); (void)heartbeat_init(); (void)agent_loop_init(); diff --git a/apps/mimiclaw/mimi_base.h b/apps/mimiclaw/mimi_base.h index 57803773f..c34ff2009 100644 --- a/apps/mimiclaw/mimi_base.h +++ b/apps/mimiclaw/mimi_base.h @@ -1,6 +1,7 @@ #pragma once #include "tal_api.h" +#include "mimi_config.h" #include #include @@ -9,6 +10,50 @@ #include #include +#if MIMI_USE_SDCARD +#include "tkl_fs.h" +#define mimi_fopen tkl_fopen +#define mimi_fclose tkl_fclose +#define mimi_fread tkl_fread +#define mimi_fwrite tkl_fwrite +#define mimi_fseek tkl_fseek +#define mimi_ftell tkl_ftell +#define mimi_fgets tkl_fgets +#define mimi_feof tkl_feof +#define mimi_fs_is_exist tkl_fs_is_exist +#define mimi_fs_remove tkl_fs_remove +#define mimi_fs_mkdir tkl_fs_mkdir +#define mimi_fs_rename tkl_fs_rename +#define mimi_dir_open tkl_dir_open +#define mimi_dir_close tkl_dir_close +#define mimi_dir_read tkl_dir_read +#define mimi_dir_name tkl_dir_name +#define mimi_dir_is_directory tkl_dir_is_directory +#define mimi_dir_is_regular tkl_dir_is_regular +#define mimi_fgetsize tkl_fgetsize +#else +#include "tal_fs.h" +#define mimi_fopen tal_fopen +#define mimi_fclose tal_fclose +#define mimi_fread tal_fread +#define mimi_fwrite tal_fwrite +#define mimi_fseek tal_fseek +#define mimi_ftell tal_ftell +#define mimi_fgets tal_fgets +#define mimi_feof tal_feof +#define mimi_fs_is_exist tal_fs_is_exist +#define mimi_fs_remove tal_fs_remove +#define mimi_fs_mkdir tal_fs_mkdir +#define mimi_fs_rename tal_fs_rename +#define mimi_dir_open tal_dir_open +#define mimi_dir_close tal_dir_close +#define mimi_dir_read tal_dir_read +#define mimi_dir_name tal_dir_name +#define mimi_dir_is_directory tal_dir_is_directory +#define mimi_dir_is_regular tal_dir_is_regular +#define mimi_fgetsize tal_fgetsize +#endif + #define MIMI_LOGE(tag, fmt, ...) PR_ERR("[%s] " fmt, tag, ##__VA_ARGS__) #define MIMI_LOGW(tag, fmt, ...) PR_WARN("[%s] " fmt, tag, ##__VA_ARGS__) #define MIMI_LOGI(tag, fmt, ...) PR_INFO("[%s] " fmt, tag, ##__VA_ARGS__) diff --git a/apps/mimiclaw/mimi_config.h b/apps/mimiclaw/mimi_config.h index 405c693f7..4ff67911d 100644 --- a/apps/mimiclaw/mimi_config.h +++ b/apps/mimiclaw/mimi_config.h @@ -56,6 +56,11 @@ #define MIMI_SECRET_SEARCH_KEY "" #endif +/* Search Engine Selection: 0 = Brave, 1 = Baidu */ +#ifndef MIMI_USE_BAIDU_SEARCH +#define MIMI_USE_BAIDU_SEARCH 0 +#endif + /* WiFi */ #define MIMI_WIFI_MAX_RETRY 10 #define MIMI_WIFI_RETRY_BASE_MS 1000 @@ -135,24 +140,34 @@ #define MIMI_OUTBOUND_PRIO 5 #define MIMI_OUTBOUND_CORE 0 -/* Memory / SPIFFS */ -#define MIMI_SPIFFS_BASE "/spiffs" -#define MIMI_SPIFFS_CONFIG_DIR "/spiffs/config" -#define MIMI_SPIFFS_MEMORY_DIR "/spiffs/memory" -#define MIMI_SPIFFS_SESSION_DIR "/spiffs/sessions" -#define MIMI_MEMORY_FILE "/spiffs/memory/MEMORY.md" -#define MIMI_SOUL_FILE "/spiffs/config/SOUL.md" -#define MIMI_USER_FILE "/spiffs/config/USER.md" -#define MIMI_SPIFFS_SKILLS_DIR "/spiffs/skills" -#define MIMI_SKILLS_PREFIX "/spiffs/skills/" +/* Memory / Storage */ +#ifndef MIMI_USE_SDCARD +#define MIMI_USE_SDCARD 1 +#endif + +#if MIMI_USE_SDCARD +#define MIMI_FS_BASE "/spiffs" +#else +#define MIMI_FS_BASE "/spiffs" +#endif + +#define MIMI_SPIFFS_BASE MIMI_FS_BASE +#define MIMI_SPIFFS_CONFIG_DIR MIMI_FS_BASE "/config" +#define MIMI_SPIFFS_MEMORY_DIR MIMI_FS_BASE "/memory" +#define MIMI_SPIFFS_SESSION_DIR MIMI_FS_BASE "/sessions" +#define MIMI_MEMORY_FILE MIMI_FS_BASE "/memory/MEMORY.md" +#define MIMI_SOUL_FILE MIMI_FS_BASE "/config/SOUL.md" +#define MIMI_USER_FILE MIMI_FS_BASE "/config/USER.md" +#define MIMI_SPIFFS_SKILLS_DIR MIMI_FS_BASE "/skills" +#define MIMI_SKILLS_PREFIX MIMI_FS_BASE "/skills/" #define MIMI_CONTEXT_BUF_SIZE (16 * 1024) #define MIMI_SESSION_MAX_MSGS 20 /* Cron / Heartbeat */ -#define MIMI_CRON_FILE "/spiffs/cron.json" +#define MIMI_CRON_FILE MIMI_FS_BASE "/cron.json" #define MIMI_CRON_MAX_JOBS 16 #define MIMI_CRON_CHECK_INTERVAL_MS (60 * 1000) -#define MIMI_HEARTBEAT_FILE "/spiffs/HEARTBEAT.md" +#define MIMI_HEARTBEAT_FILE MIMI_FS_BASE "/HEARTBEAT.md" #define MIMI_HEARTBEAT_INTERVAL_MS (30 * 60 * 1000) /* WebSocket Gateway */ diff --git a/apps/mimiclaw/skills/skill_loader.c b/apps/mimiclaw/skills/skill_loader.c index e3574e101..50f6f55c3 100644 --- a/apps/mimiclaw/skills/skill_loader.c +++ b/apps/mimiclaw/skills/skill_loader.c @@ -2,7 +2,7 @@ #include "mimi_config.h" -#include "tal_fs.h" +// #include "tal_fs.h" #include @@ -109,19 +109,19 @@ static void install_builtin(const builtin_skill_t *skill) snprintf(path, sizeof(path), "%s%s.md", MIMI_SKILLS_PREFIX, skill->filename); BOOL_T exists = FALSE; - if (tal_fs_is_exist(path, &exists) == OPRT_OK && exists) { + if (mimi_fs_is_exist(path, &exists) == OPRT_OK && exists) { MIMI_LOGD(TAG, "skill exists: %s", path); return; } - TUYA_FILE f = tal_fopen(path, "w"); + TUYA_FILE f = mimi_fopen(path, "w"); if (!f) { MIMI_LOGE(TAG, "cannot write skill: %s", path); return; } - int wn = tal_fwrite((void *)skill->content, (int)strlen(skill->content), f); - tal_fclose(f); + int wn = mimi_fwrite((void *)skill->content, (int)strlen(skill->content), f); + mimi_fclose(f); if (wn < 0) { MIMI_LOGE(TAG, "write skill failed: %s", path); return; @@ -132,9 +132,11 @@ static void install_builtin(const builtin_skill_t *skill) OPERATE_RET skill_loader_init(void) { + MIMI_LOGI(TAG, "skill_loader_init: loading %d built-in skills, dir=%s", NUM_BUILTINS, MIMI_SPIFFS_SKILLS_DIR); + BOOL_T exists = FALSE; - if (tal_fs_is_exist(MIMI_SPIFFS_SKILLS_DIR, &exists) != OPRT_OK || !exists) { - int mk_rt = tal_fs_mkdir(MIMI_SPIFFS_SKILLS_DIR); + if (mimi_fs_is_exist(MIMI_SPIFFS_SKILLS_DIR, &exists) != OPRT_OK || !exists) { + int mk_rt = mimi_fs_mkdir(MIMI_SPIFFS_SKILLS_DIR); if (mk_rt != OPRT_OK) { MIMI_LOGE(TAG, "mkdir failed: %s rt=%d", MIMI_SPIFFS_SKILLS_DIR, mk_rt); return mk_rt; @@ -142,6 +144,7 @@ OPERATE_RET skill_loader_init(void) } for (int i = 0; i < NUM_BUILTINS; i++) { + MIMI_LOGI(TAG, "installing built-in skill [%d/%d]: %s", i + 1, NUM_BUILTINS, s_builtins[i].filename); install_builtin(&s_builtins[i]); } @@ -180,7 +183,7 @@ static void extract_description(TUYA_FILE f, char *out, size_t out_size) size_t off = 0; char line[256] = {0}; - while (tal_fgets(line, sizeof(line), f) && off < out_size - 1) { + while (mimi_fgets(line, sizeof(line), f) && off < out_size - 1) { size_t len = strlen(line); if (len == 0 || (len == 1 && line[0] == '\n') || (len >= 2 && line[0] == '#' && line[1] == '#')) { @@ -213,7 +216,7 @@ size_t skill_loader_build_summary(char *buf, size_t size) } TUYA_DIR dir = NULL; - if (tal_dir_open(MIMI_SPIFFS_SKILLS_DIR, &dir) != OPRT_OK || !dir) { + if (mimi_dir_open(MIMI_SPIFFS_SKILLS_DIR, &dir) != OPRT_OK || !dir) { MIMI_LOGW(TAG, "cannot open skills dir: %s", MIMI_SPIFFS_SKILLS_DIR); buf[0] = '\0'; return 0; @@ -223,12 +226,12 @@ size_t skill_loader_build_summary(char *buf, size_t size) while (off < size - 1) { TUYA_FILEINFO info = NULL; - if (tal_dir_read(dir, &info) != OPRT_OK || !info) { + if (mimi_dir_read(dir, &info) != OPRT_OK || !info) { break; } const char *name = NULL; - if (tal_dir_name(info, &name) != OPRT_OK || !name) { + if (mimi_dir_name(info, &name) != OPRT_OK || !name) { continue; } @@ -240,14 +243,14 @@ size_t skill_loader_build_summary(char *buf, size_t size) char full_path[256] = {0}; snprintf(full_path, sizeof(full_path), "%s/%s", MIMI_SPIFFS_SKILLS_DIR, name); - TUYA_FILE f = tal_fopen(full_path, "r"); + TUYA_FILE f = mimi_fopen(full_path, "r"); if (!f) { continue; } char first_line[128] = {0}; - if (!tal_fgets(first_line, sizeof(first_line), f)) { - tal_fclose(f); + if (!mimi_fgets(first_line, sizeof(first_line), f)) { + mimi_fclose(f); continue; } @@ -256,19 +259,19 @@ size_t skill_loader_build_summary(char *buf, size_t size) char desc[256] = {0}; extract_description(f, desc, sizeof(desc)); - tal_fclose(f); + mimi_fclose(f); off += (size_t)snprintf(buf + off, size - off, "- **%s**: %s (read with: read_file %s)\n", title, desc[0] ? desc : "(no description)", full_path); } - tal_dir_close(dir); + mimi_dir_close(dir); if (off >= size) { off = size - 1; } buf[off] = '\0'; - MIMI_LOGI(TAG, "skills summary bytes=%u", (unsigned)off); + MIMI_LOGI(TAG, "skills summary built: bytes=%u content:\n%s", (unsigned)off, off > 0 ? buf : "(empty)"); return off; } diff --git a/apps/mimiclaw/tools/tool_files.c b/apps/mimiclaw/tools/tool_files.c index fefe353ed..a6e9e7be8 100644 --- a/apps/mimiclaw/tools/tool_files.c +++ b/apps/mimiclaw/tools/tool_files.c @@ -1,7 +1,7 @@ #include "tool_files.h" #include "cJSON.h" -#include "tal_fs.h" +// #include "tal_fs.h" #include @@ -42,7 +42,7 @@ OPERATE_RET tool_read_file_execute(const char *input_json, char *output, size_t return OPRT_INVALID_PARM; } - TUYA_FILE f = tal_fopen(path, "r"); + TUYA_FILE f = mimi_fopen(path, "r"); if (!f) { cJSON_Delete(root); snprintf(output, output_size, "Error: file not found"); @@ -54,16 +54,16 @@ OPERATE_RET tool_read_file_execute(const char *input_json, char *output, size_t max_read = MAX_FILE_SIZE; } - int n = tal_fread(output, (int)max_read, f); + int n = mimi_fread(output, (int)max_read, f); if (n < 0) { - tal_fclose(f); + mimi_fclose(f); cJSON_Delete(root); snprintf(output, output_size, "Error: read failed"); return OPRT_COM_ERROR; } output[n] = '\0'; - tal_fclose(f); + mimi_fclose(f); cJSON_Delete(root); MIMI_LOGI(TAG, "read_file path=%s bytes=%u", path, (unsigned)n); return OPRT_OK; @@ -90,15 +90,15 @@ OPERATE_RET tool_write_file_execute(const char *input_json, char *output, size_t return OPRT_INVALID_PARM; } - TUYA_FILE f = tal_fopen(path, "w"); + TUYA_FILE f = mimi_fopen(path, "w"); if (!f) { cJSON_Delete(root); snprintf(output, output_size, "Error: open file failed"); return OPRT_FILE_OPEN_FAILED; } - int wn = tal_fwrite((void *)content, (int)strlen(content), f); - tal_fclose(f); + int wn = mimi_fwrite((void *)content, (int)strlen(content), f); + mimi_fclose(f); if (wn < 0) { cJSON_Delete(root); snprintf(output, output_size, "Error: write failed"); @@ -132,7 +132,7 @@ OPERATE_RET tool_edit_file_execute(const char *input_json, char *output, size_t return OPRT_INVALID_PARM; } - TUYA_FILE f = tal_fopen(path, "r"); + TUYA_FILE f = mimi_fopen(path, "r"); if (!f) { cJSON_Delete(root); snprintf(output, output_size, "Error: file not found"); @@ -141,13 +141,13 @@ OPERATE_RET tool_edit_file_execute(const char *input_json, char *output, size_t char *buf = (char *)malloc(MAX_FILE_SIZE + 1); if (!buf) { - tal_fclose(f); + mimi_fclose(f); cJSON_Delete(root); return OPRT_MALLOC_FAILED; } - int n = tal_fread(buf, MAX_FILE_SIZE, f); - tal_fclose(f); + int n = mimi_fread(buf, MAX_FILE_SIZE, f); + mimi_fclose(f); if (n < 0) { free(buf); cJSON_Delete(root); @@ -179,7 +179,7 @@ OPERATE_RET tool_edit_file_execute(const char *input_json, char *output, size_t memcpy(new_buf + prefix_len, new_s, strlen(new_s)); strcpy(new_buf + prefix_len + strlen(new_s), buf + suffix_off); - f = tal_fopen(path, "w"); + f = mimi_fopen(path, "w"); if (!f) { free(new_buf); free(buf); @@ -187,8 +187,8 @@ OPERATE_RET tool_edit_file_execute(const char *input_json, char *output, size_t return OPRT_FILE_OPEN_FAILED; } - (void)tal_fwrite(new_buf, (int)strlen(new_buf), f); - tal_fclose(f); + (void)mimi_fwrite(new_buf, (int)strlen(new_buf), f); + mimi_fclose(f); snprintf(output, output_size, "OK: edit done"); free(new_buf); @@ -217,7 +217,7 @@ OPERATE_RET tool_list_dir_execute(const char *input_json, char *output, size_t o } TUYA_DIR dir = NULL; - if (tal_dir_open(prefix, &dir) != OPRT_OK || !dir) { + if (mimi_dir_open(prefix, &dir) != OPRT_OK || !dir) { if (root) { cJSON_Delete(root); } @@ -230,19 +230,19 @@ OPERATE_RET tool_list_dir_execute(const char *input_json, char *output, size_t o while (off < output_size - 2) { TUYA_FILEINFO info = NULL; - if (tal_dir_read(dir, &info) != OPRT_OK || !info) { + if (mimi_dir_read(dir, &info) != OPRT_OK || !info) { break; } const char *name = NULL; - if (tal_dir_name(info, &name) != OPRT_OK || !name) { + if (mimi_dir_name(info, &name) != OPRT_OK || !name) { continue; } off += snprintf(output + off, output_size - off, "- %s\n", name); } - tal_dir_close(dir); + mimi_dir_close(dir); if (root) { cJSON_Delete(root); } diff --git a/apps/mimiclaw/tools/tool_get_time.c b/apps/mimiclaw/tools/tool_get_time.c index b92600f24..08921a231 100644 --- a/apps/mimiclaw/tools/tool_get_time.c +++ b/apps/mimiclaw/tools/tool_get_time.c @@ -11,7 +11,7 @@ static const char *TAG = "tool_time"; #define TIME_SYNC_HOST MIMI_TG_API_HOST -#define TIME_SYNC_PORT 443 +#define TIME_SYNC_PORT 80 #define TIME_SYNC_PATH "/" #define TIME_SYNC_TIMEOUT_MS (10 * 1000) @@ -87,11 +87,18 @@ static void ensure_timezone_from_config(void) OPERATE_RET rt = tal_time_set_time_zone_seconds(tz_sec); if (rt != OPRT_OK) { MIMI_LOGW(TAG, "set timezone from config failed rt=%d", rt); + } else { + MIMI_LOGI(TAG, "timezone set from config: %s => %d sec", MIMI_TIMEZONE, tz_sec); } } s_tz_inited = true; } +void tool_get_time_init(void) +{ + ensure_timezone_from_config(); +} + static bool header_key_match(const char *line, size_t line_len, const char *key) { size_t key_len = strlen(key); @@ -272,62 +279,72 @@ static OPERATE_RET fetch_date_direct(char *date_buf, size_t date_buf_size) return OPRT_INVALID_PARM; } - OPERATE_RET rt = ensure_time_cert(); - if (rt != OPRT_OK) { - return rt; - } + const char *sync_hosts[] = {"www.baidu.com", TIME_SYNC_HOST, "www.google.com"}; + const uint16_t sync_ports[] = {80, 443}; + const char *methods[] = {"GET", "HEAD"}; - http_client_header_t headers[] = { - {.key = "Connection", .value = "close"}, - }; + OPERATE_RET last_rt = OPRT_NOT_FOUND; - OPERATE_RET last_rt = OPRT_NOT_FOUND; - const char *methods[] = {"GET", "HEAD"}; - for (size_t i = 0; i < sizeof(methods) / sizeof(methods[0]); i++) { - const char *method = methods[i]; - - http_client_response_t response = {0}; - http_client_status_t http_rt = http_client_request( - &(const http_client_request_t){ - .cacert = s_time_cacert, - .cacert_len = s_time_cacert_len, - .tls_no_verify = s_time_tls_no_verify, - .host = TIME_SYNC_HOST, - .port = TIME_SYNC_PORT, - .method = method, - .path = TIME_SYNC_PATH, - .headers = headers, - .headers_count = (uint8_t)(sizeof(headers) / sizeof(headers[0])), - .body = (const uint8_t *)"", - .body_length = 0, - .timeout_ms = TIME_SYNC_TIMEOUT_MS, - }, - &response); - - if (http_rt != HTTP_CLIENT_SUCCESS) { - MIMI_LOGW(TAG, "direct %s request failed: %d", method, http_rt); - last_rt = OPRT_LINK_CORE_HTTP_CLIENT_SEND_ERROR; - continue; - } + for (size_t h = 0; h < sizeof(sync_hosts) / sizeof(sync_hosts[0]); h++) { + const char *host = sync_hosts[h]; - bool ok = false; - if (response.headers && response.headers_length > 0) { - ok = find_header_value((const char *)response.headers, response.headers_length, "Date", date_buf, - date_buf_size); - } + for (size_t p = 0; p < sizeof(sync_ports) / sizeof(sync_ports[0]); p++) { + uint16_t port = sync_ports[p]; + bool is_https = (port == 443); - uint16_t status = response.status_code; - http_client_free(&response); - if (status < 200 || status >= 400) { - MIMI_LOGW(TAG, "direct %s time sync http status=%u", method, status); - } + if (is_https) { + ensure_time_cert(); + } - if (ok) { - return OPRT_OK; + for (size_t m = 0; m < sizeof(methods) / sizeof(methods[0]); m++) { + const char *method = methods[m]; + + http_client_header_t headers[] = { + {.key = "Connection", .value = "close"}, + }; + + http_client_response_t response = {0}; + http_client_status_t http_rt = http_client_request( + &(const http_client_request_t){ + .cacert = is_https ? s_time_cacert : NULL, + .cacert_len = is_https ? s_time_cacert_len : 0, + .tls_no_verify = is_https ? s_time_tls_no_verify : false, + .host = host, + .port = port, + .method = method, + .path = TIME_SYNC_PATH, + .headers = headers, + .headers_count = (uint8_t)(sizeof(headers) / sizeof(headers[0])), + .body = (const uint8_t *)"", + .body_length = 0, + .timeout_ms = TIME_SYNC_TIMEOUT_MS, + }, + &response); + + if (http_rt != HTTP_CLIENT_SUCCESS) { + MIMI_LOGW(TAG, "direct %s %s:%d failed: %d", method, host, port, http_rt); + last_rt = OPRT_LINK_CORE_HTTP_CLIENT_SEND_ERROR; + continue; + } + + bool ok = false; + if (response.headers && response.headers_length > 0) { + ok = find_header_value((const char *)response.headers, response.headers_length, "Date", date_buf, + date_buf_size); + } + + uint16_t status = response.status_code; + http_client_free(&response); + + if (ok) { + MIMI_LOGI(TAG, "date fetched from %s:%d via %s, status=%u", host, port, method, status); + return OPRT_OK; + } + + MIMI_LOGW(TAG, "direct %s %s:%d status=%u missing Date header", method, host, port, status); + last_rt = OPRT_NOT_FOUND; + } } - - MIMI_LOGW(TAG, "direct %s response missing Date header", method); - last_rt = OPRT_NOT_FOUND; } return last_rt; @@ -339,65 +356,80 @@ static OPERATE_RET fetch_date_via_proxy(char *date_buf, size_t date_buf_size) return OPRT_INVALID_PARM; } - OPERATE_RET last_rt = OPRT_NOT_FOUND; - const char *methods[] = {"GET", "HEAD"}; - for (size_t i = 0; i < sizeof(methods) / sizeof(methods[0]); i++) { - const char *method = methods[i]; - proxy_conn_t *conn = proxy_conn_open(TIME_SYNC_HOST, TIME_SYNC_PORT, TIME_SYNC_TIMEOUT_MS); - if (!conn) { - last_rt = OPRT_LINK_CORE_NET_CONNECT_ERROR; - continue; - } - - char req[256] = {0}; - int req_len = snprintf(req, sizeof(req), - "%s %s HTTP/1.1\r\n" - "Host: " TIME_SYNC_HOST "\r\n" - "Connection: close\r\n\r\n", - method, TIME_SYNC_PATH); - if (req_len <= 0 || req_len >= (int)sizeof(req)) { - proxy_conn_close(conn); - return OPRT_COM_ERROR; - } - - if (proxy_conn_write(conn, req, req_len) < 0) { - proxy_conn_close(conn); - MIMI_LOGW(TAG, "proxy %s request write failed", method); - last_rt = OPRT_LINK_CORE_NET_SOCKET_ERROR; - continue; - } - - char raw[1024] = {0}; - int total = 0; - while (total < (int)sizeof(raw) - 1) { - int n = proxy_conn_read(conn, raw + total, (int)sizeof(raw) - 1 - total, TIME_SYNC_TIMEOUT_MS); - if (n == OPRT_RESOURCE_NOT_READY) { - continue; - } - if (n <= 0) { - break; - } - total += n; - raw[total] = '\0'; - if (strstr(raw, "\r\n\r\n")) { - break; + const char *sync_hosts[] = {"www.baidu.com", TIME_SYNC_HOST, "www.google.com"}; + const uint16_t sync_ports[] = {80, 443}; + const char *methods[] = {"GET", "HEAD"}; + + OPERATE_RET last_rt = OPRT_NOT_FOUND; + + for (size_t h = 0; h < sizeof(sync_hosts) / sizeof(sync_hosts[0]); h++) { + const char *host = sync_hosts[h]; + + for (size_t p = 0; p < sizeof(sync_ports) / sizeof(sync_ports[0]); p++) { + uint16_t port = sync_ports[p]; + + for (size_t mi = 0; mi < sizeof(methods) / sizeof(methods[0]); mi++) { + const char *method = methods[mi]; + + proxy_conn_t *conn = proxy_conn_open(host, port, TIME_SYNC_TIMEOUT_MS); + if (!conn) { + MIMI_LOGW(TAG, "proxy connect %s:%d failed", host, port); + last_rt = OPRT_LINK_CORE_NET_CONNECT_ERROR; + continue; + } + + char req[256] = {0}; + int req_len = snprintf(req, sizeof(req), + "%s %s HTTP/1.1\r\n" + "Host: %s\r\n" + "Connection: close\r\n\r\n", + method, TIME_SYNC_PATH, host); + if (req_len <= 0 || req_len >= (int)sizeof(req)) { + proxy_conn_close(conn); + return OPRT_COM_ERROR; + } + + if (proxy_conn_write(conn, req, req_len) < 0) { + proxy_conn_close(conn); + MIMI_LOGW(TAG, "proxy %s write %s:%d failed", method, host, port); + last_rt = OPRT_LINK_CORE_NET_SOCKET_ERROR; + continue; + } + + char raw[1024] = {0}; + int total = 0; + while (total < (int)sizeof(raw) - 1) { + int n = proxy_conn_read(conn, raw + total, (int)sizeof(raw) - 1 - total, TIME_SYNC_TIMEOUT_MS); + if (n == OPRT_RESOURCE_NOT_READY) { + continue; + } + if (n <= 0) { + break; + } + total += n; + raw[total] = '\0'; + if (strstr(raw, "\r\n\r\n")) { + break; + } + } + proxy_conn_close(conn); + + if (total <= 0) { + MIMI_LOGW(TAG, "proxy %s %s:%d empty response", method, host, port); + last_rt = OPRT_COM_ERROR; + continue; + } + + bool ok = find_header_value(raw, (size_t)total, "Date", date_buf, date_buf_size); + if (ok) { + MIMI_LOGI(TAG, "date fetched via proxy from %s:%d using %s", host, port, method); + return OPRT_OK; + } + + MIMI_LOGW(TAG, "proxy %s %s:%d missing Date header", method, host, port); + last_rt = OPRT_NOT_FOUND; } } - proxy_conn_close(conn); - - if (total <= 0) { - MIMI_LOGW(TAG, "proxy %s request got empty response", method); - last_rt = OPRT_COM_ERROR; - continue; - } - - bool ok = find_header_value(raw, (size_t)total, "Date", date_buf, date_buf_size); - if (ok) { - return OPRT_OK; - } - - MIMI_LOGW(TAG, "proxy %s response missing Date header", method); - last_rt = OPRT_NOT_FOUND; } return last_rt; @@ -443,3 +475,9 @@ OPERATE_RET tool_get_time_execute(const char *input_json, char *output, size_t o output); return OPRT_OK; } + +OPERATE_RET tool_get_time_sync_now(void) +{ + char out[128] = {0}; + return tool_get_time_execute("{}", out, sizeof(out)); +} diff --git a/apps/mimiclaw/tools/tool_get_time.h b/apps/mimiclaw/tools/tool_get_time.h index b9c4cbdde..3ce28f6c0 100755 --- a/apps/mimiclaw/tools/tool_get_time.h +++ b/apps/mimiclaw/tools/tool_get_time.h @@ -2,4 +2,9 @@ #include "mimi_base.h" +/** + * @brief Initialize timezone from MIMI_TIMEZONE config. Call once at startup. + */ +void tool_get_time_init(void); + OPERATE_RET tool_get_time_execute(const char *input_json, char *output, size_t output_size); diff --git a/apps/mimiclaw/tools/tool_led.c b/apps/mimiclaw/tools/tool_led.c new file mode 100644 index 000000000..4c11fc4d2 --- /dev/null +++ b/apps/mimiclaw/tools/tool_led.c @@ -0,0 +1,144 @@ +#include "tool_led.h" + +#include "cJSON.h" +#include "tdl_led_manage.h" +#include "tdd_led_gpio.h" +#define BOARD_LED_PIN TUYA_GPIO_NUM_1 +#define BOARD_LED_ACTIVE_LV TUYA_GPIO_LEVEL_HIGH +static const char *TAG = "tool_led"; + +static TDL_LED_HANDLE_T sg_led_hdl = NULL; + +OPERATE_RET tool_led_init(void) +{ +#if defined(LED_NAME) && defined(BOARD_LED_PIN) + TDD_LED_GPIO_CFG_T led_gpio; + + led_gpio.pin = BOARD_LED_PIN; + led_gpio.level = BOARD_LED_ACTIVE_LV; + led_gpio.mode = TUYA_GPIO_PUSH_PULL; + + OPERATE_RET rt = tdd_led_gpio_register(LED_NAME, &led_gpio); + if (rt != OPRT_OK) { + MIMI_LOGE(TAG, "led gpio register failed: %d", rt); + return rt; + } + + sg_led_hdl = tdl_led_find_dev(LED_NAME); + if (!sg_led_hdl) { + MIMI_LOGE(TAG, "led device not found: %s", LED_NAME); + return OPRT_NOT_FOUND; + } + + rt = tdl_led_open(sg_led_hdl); + if (rt != OPRT_OK) { + MIMI_LOGE(TAG, "led open failed: %d", rt); + return rt; + } + + /* Start with LED off */ + tdl_led_set_status(sg_led_hdl, TDL_LED_OFF); + + MIMI_LOGI(TAG, "led init ok, name=%s", LED_NAME); +#else + MIMI_LOGW(TAG, "led not available: LED_NAME or BOARD_LED_PIN not defined"); +#endif + return OPRT_OK; +} + +OPERATE_RET tool_led_control_execute(const char *input_json, char *output, size_t output_size) +{ + if (!input_json || !output || output_size == 0) { + return OPRT_INVALID_PARM; + } + + output[0] = '\0'; + + if (!sg_led_hdl) { + snprintf(output, output_size, "Error: LED not initialized or not available on this board"); + return OPRT_NOT_FOUND; + } + + cJSON *root = cJSON_Parse(input_json); + if (!root) { + snprintf(output, output_size, "Error: invalid JSON input"); + return OPRT_CJSON_PARSE_ERR; + } + + const char *action = cJSON_GetStringValue(cJSON_GetObjectItem(root, "action")); + if (!action || action[0] == '\0') { + cJSON_Delete(root); + snprintf(output, output_size, "Error: missing 'action' parameter"); + return OPRT_INVALID_PARM; + } + + OPERATE_RET rt = OPRT_OK; + + if (strcmp(action, "on") == 0) { + rt = tdl_led_set_status(sg_led_hdl, TDL_LED_ON); + if (rt == OPRT_OK) { + snprintf(output, output_size, "LED turned on"); + } else { + snprintf(output, output_size, "Error: failed to turn on LED (rt=%d)", rt); + } + + } else if (strcmp(action, "off") == 0) { + rt = tdl_led_set_status(sg_led_hdl, TDL_LED_OFF); + if (rt == OPRT_OK) { + snprintf(output, output_size, "LED turned off"); + } else { + snprintf(output, output_size, "Error: failed to turn off LED (rt=%d)", rt); + } + + } else if (strcmp(action, "toggle") == 0) { + rt = tdl_led_set_status(sg_led_hdl, TDL_LED_TOGGLE); + if (rt == OPRT_OK) { + snprintf(output, output_size, "LED toggled"); + } else { + snprintf(output, output_size, "Error: failed to toggle LED (rt=%d)", rt); + } + + } else if (strcmp(action, "blink") == 0) { + cJSON *count_j = cJSON_GetObjectItem(root, "count"); + cJSON *interval_j = cJSON_GetObjectItem(root, "interval_ms"); + + uint32_t count = cJSON_IsNumber(count_j) ? (uint32_t)count_j->valuedouble : 5; + uint32_t interval_ms = cJSON_IsNumber(interval_j) ? (uint32_t)interval_j->valuedouble : 300; + + TDL_LED_BLINK_CFG_T blink_cfg = { + .cnt = count, + .start_stat = TDL_LED_ON, + .first_half_cycle_time = interval_ms, + .latter_half_cycle_time = interval_ms, + }; + + rt = tdl_led_blink(sg_led_hdl, &blink_cfg); + if (rt == OPRT_OK) { + snprintf(output, output_size, "LED blinking %u times with %ums interval", (unsigned)count, + (unsigned)interval_ms); + } else { + snprintf(output, output_size, "Error: failed to blink LED (rt=%d)", rt); + } + + } else if (strcmp(action, "flash") == 0) { + cJSON *interval_j = cJSON_GetObjectItem(root, "interval_ms"); + + uint32_t interval_ms = cJSON_IsNumber(interval_j) ? (uint32_t)interval_j->valuedouble : 1000; + + rt = tdl_led_flash(sg_led_hdl, interval_ms); + if (rt == OPRT_OK) { + snprintf(output, output_size, "LED flashing with %ums half cycle", (unsigned)interval_ms); + } else { + snprintf(output, output_size, "Error: failed to flash LED (rt=%d)", rt); + } + + } else { + snprintf(output, output_size, "Error: unknown action '%s'. Valid actions: on, off, toggle, blink, flash", + action); + rt = OPRT_INVALID_PARM; + } + + MIMI_LOGI(TAG, "led_control action=%s rt=%d", action, rt); + cJSON_Delete(root); + return rt; +} diff --git a/apps/mimiclaw/tools/tool_led.h b/apps/mimiclaw/tools/tool_led.h new file mode 100644 index 000000000..e80e870a3 --- /dev/null +++ b/apps/mimiclaw/tools/tool_led.h @@ -0,0 +1,6 @@ +#pragma once + +#include "mimi_base.h" + +OPERATE_RET tool_led_init(void); +OPERATE_RET tool_led_control_execute(const char *input_json, char *output, size_t output_size); diff --git a/apps/mimiclaw/tools/tool_registry.c b/apps/mimiclaw/tools/tool_registry.c index ef274a894..4ece5b20d 100644 --- a/apps/mimiclaw/tools/tool_registry.c +++ b/apps/mimiclaw/tools/tool_registry.c @@ -3,13 +3,14 @@ #include "tools/tool_files.h" #include "tools/tool_cron.h" #include "tools/tool_get_time.h" +#include "tools/tool_led.h" #include "tools/tool_web_search.h" #include "cJSON.h" static const char *TAG = "tools"; -#define MAX_TOOLS 12 +#define MAX_TOOLS 16 static mimi_tool_t s_tools[MAX_TOOLS]; static int s_tool_count = 0; @@ -59,6 +60,7 @@ OPERATE_RET tool_registry_init(void) s_tool_count = 0; (void)tool_web_search_init(); + (void)tool_led_init(); register_tool(&(mimi_tool_t){ .name = "web_search", @@ -145,6 +147,21 @@ OPERATE_RET tool_registry_init(void) .execute = tool_cron_remove_execute, }); + register_tool(&(mimi_tool_t){ + .name = "led_control", + .description = "Control the onboard LED. Actions: on, off, toggle, blink (with optional count and " + "interval_ms), flash (with optional interval_ms)", + .input_schema_json = + "{\"type\":\"object\"," + "\"properties\":{" + "\"action\":{\"type\":\"string\",\"description\":\"LED action: on, off, toggle, blink, flash\"}," + "\"count\":{\"type\":\"integer\",\"description\":\"Blink count (default 5, blink only)\"}," + "\"interval_ms\":{\"type\":\"integer\",\"description\":\"Interval in ms (default 300 for blink, " + "1000 for flash)\"}}," + "\"required\":[\"action\"]}", + .execute = tool_led_control_execute, + }); + build_tools_json(); return OPRT_OK; } @@ -160,12 +177,20 @@ OPERATE_RET tool_registry_execute(const char *name, const char *input_json, char return OPRT_INVALID_PARM; } + MIMI_LOGI(TAG, "tool_execute: name=%s input_json=%s", name, input_json ? input_json : "(null)"); + for (int i = 0; i < s_tool_count; i++) { if (strcmp(name, s_tools[i].name) == 0) { - return s_tools[i].execute(input_json ? input_json : "{}", output, output_size); + uint32_t start_ms = tal_system_get_millisecond(); + OPERATE_RET rt = s_tools[i].execute(input_json ? input_json : "{}", output, output_size); + uint32_t elapsed_ms = tal_system_get_millisecond() - start_ms; + MIMI_LOGI(TAG, "tool_execute done: name=%s rt=%d elapsed=%ums output_len=%u", name, rt, + (unsigned)elapsed_ms, (unsigned)strlen(output)); + return rt; } } + MIMI_LOGW(TAG, "tool_execute: unknown tool '%s'", name); snprintf(output, output_size, "Error: unknown tool: %s", name); return OPRT_NOT_FOUND; } diff --git a/apps/mimiclaw/tools/tool_web_search.c b/apps/mimiclaw/tools/tool_web_search.c index 4c0a7ea2e..c18b90b07 100644 --- a/apps/mimiclaw/tools/tool_web_search.c +++ b/apps/mimiclaw/tools/tool_web_search.c @@ -1,8 +1,11 @@ #include "tool_web_search.h" +#include "mimi_config.h" + +#if !defined(MIMI_USE_BAIDU_SEARCH) + #include "cJSON.h" #include "http_client_interface.h" -#include "mimi_config.h" #include "tls_cert_bundle.h" static char s_search_key[128] = {0}; @@ -291,3 +294,5 @@ OPERATE_RET tool_web_search_set_key(const char *api_key) safe_copy(s_search_key, sizeof(s_search_key), api_key); return mimi_kv_set_string(MIMI_NVS_SEARCH, MIMI_NVS_KEY_API_KEY, api_key); } + +#endif /* !MIMI_USE_BAIDU_SEARCH */ diff --git a/apps/mimiclaw/tools/tool_web_search_baidu.c b/apps/mimiclaw/tools/tool_web_search_baidu.c new file mode 100644 index 000000000..1e09a16de --- /dev/null +++ b/apps/mimiclaw/tools/tool_web_search_baidu.c @@ -0,0 +1,297 @@ +#include "tool_web_search.h" + +#include "mimi_config.h" + +#if MIMI_USE_BAIDU_SEARCH + +#include "cJSON.h" +#include "http_client_interface.h" +#include "tls_cert_bundle.h" + +static char s_search_key[256] = {0}; +static uint8_t *s_search_cacert = NULL; +static size_t s_search_cacert_len = 0; +static bool s_search_tls_no_verify = false; + +static const char *TAG = "web_search_baidu"; + +#define SEARCH_HOST "qianfan.baidubce.com" +#define SEARCH_PATH "/v2/ai_search/chat/completions" +#define SEARCH_RESULT_COUNT 5 +#define SEARCH_TIMEOUT_MS (30 * 1000) +#define SEARCH_RESP_BUF_SIZE (32 * 1024) + +static void safe_copy(char *dst, size_t dst_size, const char *src) +{ + if (!dst || dst_size == 0) { + return; + } + if (!src) { + dst[0] = '\0'; + return; + } + snprintf(dst, dst_size, "%s", src); +} + +static OPERATE_RET ensure_search_cert(void) +{ + if (s_search_cacert && s_search_cacert_len > 0) { + s_search_tls_no_verify = false; + return OPRT_OK; + } + + OPERATE_RET rt = mimi_tls_query_domain_certs(SEARCH_HOST, &s_search_cacert, &s_search_cacert_len); + if (rt != OPRT_OK || !s_search_cacert || s_search_cacert_len == 0) { + if (s_search_cacert) { + tal_free(s_search_cacert); + } + s_search_cacert = NULL; + s_search_cacert_len = 0; + s_search_tls_no_verify = true; + MIMI_LOGW(TAG, "cert unavailable for %s, fallback to TLS no-verify mode rt=%d", SEARCH_HOST, rt); + return OPRT_OK; + } + + s_search_tls_no_verify = false; + return OPRT_OK; +} + +static void format_results(cJSON *root, char *output, size_t output_size) +{ + if (!output || output_size == 0) { + return; + } + output[0] = '\0'; + + cJSON *refs = cJSON_GetObjectItem(root, "references"); + if (!cJSON_IsArray(refs) || cJSON_GetArraySize(refs) == 0) { + snprintf(output, output_size, "No web results found."); + return; + } + + size_t off = 0; + int idx = 0; + cJSON *item = NULL; + cJSON_ArrayForEach(item, refs) + { + if (idx >= SEARCH_RESULT_COUNT || off >= output_size - 1) { + break; + } + + cJSON *title = cJSON_GetObjectItem(item, "title"); + cJSON *url = cJSON_GetObjectItem(item, "url"); + cJSON *content = cJSON_GetObjectItem(item, "content"); + + const char *title_s = cJSON_IsString(title) ? title->valuestring : "(no title)"; + const char *url_s = cJSON_IsString(url) ? url->valuestring : ""; + const char *content_s = cJSON_IsString(content) ? content->valuestring : ""; + + int n = + snprintf(output + off, output_size - off, "%d. %s\n %s\n %s\n\n", idx + 1, title_s, url_s, content_s); + if (n <= 0) { + break; + } + if ((size_t)n >= output_size - off) { + off = output_size - 1; + output[off] = '\0'; + break; + } + + off += (size_t)n; + idx++; + } + + if (idx == 0) { + snprintf(output, output_size, "No web results found."); + } +} + +static OPERATE_RET search_http_call(const char *body, size_t body_len, char *resp_buf, size_t resp_size, + uint16_t *status_code) +{ + if (!body || !resp_buf || resp_size == 0) { + return OPRT_INVALID_PARM; + } + + OPERATE_RET rt = ensure_search_cert(); + if (rt != OPRT_OK) { + return rt; + } + + char auth_value[300] = {0}; + snprintf(auth_value, sizeof(auth_value), "Bearer %s", s_search_key); + + http_client_header_t headers[2] = {0}; + uint8_t header_count = 0; + headers[header_count++] = (http_client_header_t){ + .key = "Content-Type", + .value = "application/json", + }; + headers[header_count++] = (http_client_header_t){ + .key = "Authorization", + .value = auth_value, + }; + + http_client_response_t response = {0}; + http_client_status_t http_rt = http_client_request( + &(const http_client_request_t){ + .cacert = s_search_cacert, + .cacert_len = s_search_cacert_len, + .tls_no_verify = s_search_tls_no_verify, + .host = SEARCH_HOST, + .port = 443, + .method = "POST", + .path = SEARCH_PATH, + .headers = headers, + .headers_count = header_count, + .body = (const uint8_t *)body, + .body_length = body_len, + .timeout_ms = SEARCH_TIMEOUT_MS, + }, + &response); + + if (http_rt != HTTP_CLIENT_SUCCESS) { + MIMI_LOGE(TAG, "http request failed: %d", http_rt); + return OPRT_LINK_CORE_HTTP_CLIENT_SEND_ERROR; + } + + if (status_code) { + *status_code = response.status_code; + } + + resp_buf[0] = '\0'; + if (response.body && response.body_length > 0) { + size_t copy = (response.body_length < resp_size - 1) ? response.body_length : (resp_size - 1); + memcpy(resp_buf, response.body, copy); + resp_buf[copy] = '\0'; + } + + http_client_free(&response); + return OPRT_OK; +} + +OPERATE_RET tool_web_search_init(void) +{ + if (MIMI_SECRET_SEARCH_KEY[0] != '\0') { + safe_copy(s_search_key, sizeof(s_search_key), MIMI_SECRET_SEARCH_KEY); + } + + char tmp[256] = {0}; + if (mimi_kv_get_string(MIMI_NVS_SEARCH, MIMI_NVS_KEY_API_KEY, tmp, sizeof(tmp)) == OPRT_OK) { + safe_copy(s_search_key, sizeof(s_search_key), tmp); + } + + MIMI_LOGI(TAG, "baidu web search init credential=%s", s_search_key[0] ? "configured" : "empty"); + return OPRT_OK; +} + +OPERATE_RET tool_web_search_execute(const char *input_json, char *output, size_t output_size) +{ + if (!input_json || !output || output_size == 0) { + return OPRT_INVALID_PARM; + } + + output[0] = '\0'; + + if (s_search_key[0] == '\0') { + snprintf(output, output_size, "Error: No Baidu search API key configured. Use CLI: set_search_key "); + return OPRT_NOT_FOUND; + } + + cJSON *input = cJSON_Parse(input_json); + if (!input) { + snprintf(output, output_size, "Error: invalid JSON input"); + return OPRT_CJSON_PARSE_ERR; + } + + cJSON *query = cJSON_GetObjectItem(input, "query"); + if (!cJSON_IsString(query) || !query->valuestring || query->valuestring[0] == '\0') { + cJSON_Delete(input); + snprintf(output, output_size, "Error: missing 'query'"); + return OPRT_INVALID_PARM; + } + + /* Build request body for Baidu AI Search API */ + cJSON *req_body = cJSON_CreateObject(); + if (!req_body) { + cJSON_Delete(input); + snprintf(output, output_size, "Error: alloc request body failed"); + return OPRT_MALLOC_FAILED; + } + + cJSON *messages = cJSON_CreateArray(); + cJSON *msg = cJSON_CreateObject(); + cJSON_AddStringToObject(msg, "role", "user"); + cJSON_AddStringToObject(msg, "content", query->valuestring); + cJSON_AddItemToArray(messages, msg); + cJSON_AddItemToObject(req_body, "messages", messages); + cJSON_Delete(input); + + cJSON_AddFalseToObject(req_body, "stream"); + cJSON_AddStringToObject(req_body, "search_source", "baidu_search_v1"); + + /* resource_type_filter: only web results */ + cJSON *res_filter = cJSON_CreateArray(); + cJSON *web_filter = cJSON_CreateObject(); + cJSON_AddStringToObject(web_filter, "type", "web"); + cJSON_AddNumberToObject(web_filter, "top_k", SEARCH_RESULT_COUNT); + cJSON_AddItemToArray(res_filter, web_filter); + cJSON_AddItemToObject(req_body, "resource_type_filter", res_filter); + + char *body_str = cJSON_PrintUnformatted(req_body); + cJSON_Delete(req_body); + + if (!body_str) { + snprintf(output, output_size, "Error: serialize request body failed"); + return OPRT_MALLOC_FAILED; + } + + size_t body_len = strlen(body_str); + + char *resp = tal_malloc(SEARCH_RESP_BUF_SIZE); + if (!resp) { + cJSON_free(body_str); + snprintf(output, output_size, "Error: alloc search response buffer failed"); + return OPRT_MALLOC_FAILED; + } + memset(resp, 0, SEARCH_RESP_BUF_SIZE); + + uint16_t status = 0; + OPERATE_RET rt = search_http_call(body_str, body_len, resp, SEARCH_RESP_BUF_SIZE, &status); + cJSON_free(body_str); + + if (rt != OPRT_OK) { + snprintf(output, output_size, "Error: search request failed (rt=%d)", rt); + tal_free(resp); + return rt; + } + + if (status != 200) { + snprintf(output, output_size, "Error: Baidu search API http=%u body=%.200s", status, resp); + tal_free(resp); + return OPRT_COM_ERROR; + } + + cJSON *root = cJSON_Parse(resp); + tal_free(resp); + if (!root) { + snprintf(output, output_size, "Error: parse search response failed"); + return OPRT_CJSON_PARSE_ERR; + } + + format_results(root, output, output_size); + cJSON_Delete(root); + return OPRT_OK; +} + +OPERATE_RET tool_web_search_set_key(const char *api_key) +{ + if (!api_key || api_key[0] == '\0') { + return OPRT_INVALID_PARM; + } + + safe_copy(s_search_key, sizeof(s_search_key), api_key); + return mimi_kv_set_string(MIMI_NVS_SEARCH, MIMI_NVS_KEY_API_KEY, api_key); +} + +#endif /* MIMI_USE_BAIDU_SEARCH */ diff --git a/boards/ESP32/WAVESHARE_ESP32C6_DEV_KIT_N16/board_com_api.c b/boards/ESP32/WAVESHARE_ESP32C6_DEV_KIT_N16/board_com_api.c index 997b13f7f..fbe071b20 100644 --- a/boards/ESP32/WAVESHARE_ESP32C6_DEV_KIT_N16/board_com_api.c +++ b/boards/ESP32/WAVESHARE_ESP32C6_DEV_KIT_N16/board_com_api.c @@ -15,10 +15,10 @@ /*********************************************************** ************************macro define************************ ***********************************************************/ -#define BOARD_BUTTON_PIN TUYA_GPIO_NUM_9 -#define BOARD_BUTTON_ACTIVE_LV TUYA_GPIO_LEVEL_LOW +#define BOARD_BUTTON_PIN TUYA_GPIO_NUM_9 +#define BOARD_BUTTON_ACTIVE_LV TUYA_GPIO_LEVEL_LOW -#define BOARD_LED_PIN TUYA_GPIO_NUM_8 +#define BOARD_LED_PIN TUYA_GPIO_NUM_8 /*********************************************************** ***********************typedef define*********************** ***********************************************************/ @@ -40,9 +40,9 @@ static OPERATE_RET __board_register_button(void) OPERATE_RET rt = OPRT_OK; BUTTON_GPIO_CFG_T button_hw_cfg = { - .pin = BOARD_BUTTON_PIN, - .level = BOARD_BUTTON_ACTIVE_LV, - .mode = BUTTON_TIMER_SCAN_MODE, + .pin = BOARD_BUTTON_PIN, + .level = BOARD_BUTTON_ACTIVE_LV, + .mode = BUTTON_TIMER_SCAN_MODE, .pin_type.gpio_pull = TUYA_GPIO_PULLUP, }; @@ -56,15 +56,15 @@ static OPERATE_RET __board_register_led(void) OPERATE_RET rt = OPRT_OK; TDD_LED_WS1280_CFG_T led_hw_cfg = { - .gpio = BOARD_LED_PIN, + .gpio = BOARD_LED_PIN, .led_count = 1, - .color = 0x00001F, // 0xRRGGBB format + .color = 0x00001F, // 0xRRGGBB format }; TUYA_CALL_ERR_RETURN(tdd_led_esp_ws1280_register(LED_NAME, &led_hw_cfg)); return OPRT_OK; -} +} /** * @brief Registers all the hardware peripherals (audio, button, LED) on the board. diff --git a/boards/ESP32/common/led/tdd_led_esp_ws1280.c b/boards/ESP32/common/led/tdd_led_esp_ws1280.c index 170736e21..671a3ed7e 100644 --- a/boards/ESP32/common/led/tdd_led_esp_ws1280.c +++ b/boards/ESP32/common/led/tdd_led_esp_ws1280.c @@ -22,15 +22,15 @@ ***********************************************************/ #define TAG "WS1280_DRIVER" -#define WS1280_COLOR_BYTES 3 // Number of bytes used by one LED color data (RGB) -#define WS1280_BITS_PER_LED (WS1280_COLOR_BYTES * 8) -#define WS1280_RESOLUTION 10000000 // 10MHz -#define WS1280_TIMEOUT_MS 100 // Transmission timeout in milliseconds -#define WS1280_T0H 4 -#define WS1280_T0L 9 -#define WS1280_T1H 8 -#define WS1280_T1L 5 -#define WS1280_RESET_US 50 +#define WS1280_COLOR_BYTES 3 // Number of bytes used by one LED color data (RGB) +#define WS1280_BITS_PER_LED (WS1280_COLOR_BYTES * 8) +#define WS1280_RESOLUTION 10000000 // 10MHz +#define WS1280_TIMEOUT_MS 100 // Transmission timeout in milliseconds +#define WS1280_T0H 4 +#define WS1280_T0L 9 +#define WS1280_T1H 8 +#define WS1280_T1L 5 +#define WS1280_RESET_US 50 /*********************************************************** ***********************typedef define*********************** ***********************************************************/ @@ -38,13 +38,13 @@ typedef struct { TDD_LED_WS1280_CFG_T cfg; uint16_t symbol_count; rmt_symbol_word_t *symbols_buf; -}TDD_LED_WS1280_INFO_T; +} TDD_LED_WS1280_INFO_T; /*********************************************************** ***********************variable define********************** ***********************************************************/ -static rmt_channel_handle_t sg_rmt_chan = NULL; -static rmt_encoder_handle_t sg_copy_encoder = NULL; // Generic copy encoder +static rmt_channel_handle_t sg_rmt_chan = NULL; +static rmt_encoder_handle_t sg_copy_encoder = NULL; // Generic copy encoder /*********************************************************** ***********************function define********************** @@ -59,15 +59,15 @@ static void __ws1280_encode_bit(rmt_symbol_word_t *sym, uint8_t bit) { if (bit) { // Logic 1 timing - sym->level0 = 1; + sym->level0 = 1; sym->duration0 = WS1280_T1H; - sym->level1 = 0; + sym->level1 = 0; sym->duration1 = WS1280_T1L; } else { // Logic 0 timing - sym->level0 = 1; + sym->level0 = 1; sym->duration0 = WS1280_T0H; - sym->level1 = 0; + sym->level1 = 0; sym->duration1 = WS1280_T0L; } } @@ -83,11 +83,11 @@ static void __ws1280_encode_data(rmt_symbol_word_t *symbols_buff, uint32_t color { uint16_t sym_idx = 0; - if(NULL == symbols_buff || led_count == 0) { + if (NULL == symbols_buff || led_count == 0) { return; } - for(int i = 0; i < led_count; i++) { + for (int i = 0; i < led_count; i++) { for (int bit = 23; bit >= 0; bit--) { __ws1280_encode_bit(&symbols_buff[sym_idx], (color >> bit) & 0x01); sym_idx++; @@ -95,9 +95,9 @@ static void __ws1280_encode_data(rmt_symbol_word_t *symbols_buff, uint32_t color } // Add reset timing (low level) - symbols_buff[sym_idx].level0 = 0; + symbols_buff[sym_idx].level0 = 0; symbols_buff[sym_idx].duration0 = WS1280_RESET_US * 10; - symbols_buff[sym_idx].level1 = 0; + symbols_buff[sym_idx].level1 = 0; symbols_buff[sym_idx].duration1 = 0; return; @@ -112,15 +112,15 @@ static esp_err_t __ws1280_init(gpio_num_t gpio_num) { esp_err_t err = ESP_OK; - // 1. Create RMT TX channel + // 1. Create RMT TX channel rmt_tx_channel_config_t tx_cfg = { - .gpio_num = gpio_num, - .clk_src = RMT_CLK_SRC_DEFAULT, - .resolution_hz = WS1280_RESOLUTION, + .gpio_num = gpio_num, + .clk_src = RMT_CLK_SRC_DEFAULT, + .resolution_hz = WS1280_RESOLUTION, .mem_block_symbols = 64, // Enough for 8 LEDs (24 bits each) plus reset timing .trans_queue_depth = 4, - .flags.invert_out = false, // Keep output level non-inverted - .flags.with_dma = false, // DMA is unnecessary for small data payloads + .flags.invert_out = false, // Keep output level non-inverted + .flags.with_dma = false, // DMA is unnecessary for small data payloads }; err = rmt_new_tx_channel(&tx_cfg, &sg_rmt_chan); if (err != ESP_OK) { @@ -129,8 +129,8 @@ static esp_err_t __ws1280_init(gpio_num_t gpio_num) } // 2. Create generic copy encoder (required because rmt_transmit encoder arg cannot be NULL) - rmt_copy_encoder_config_t copy_encoder_cfg = {}; // No special configuration is required - err = rmt_new_copy_encoder(©_encoder_cfg, &sg_copy_encoder); + rmt_copy_encoder_config_t copy_encoder_cfg = {}; // No special configuration is required + err = rmt_new_copy_encoder(©_encoder_cfg, &sg_copy_encoder); if (err != ESP_OK) { PR_ERR("rmt_new_copy_encoder failed: %s", esp_err_to_name(err)); rmt_del_channel(sg_rmt_chan); @@ -164,7 +164,7 @@ static esp_err_t __ws1280_show(rmt_symbol_word_t *symbols, uint16_t sym_count) { esp_err_t err = ESP_OK; - if(NULL == symbols || 0 == sym_count) { + if (NULL == symbols || 0 == sym_count) { return ESP_ERR_INVALID_ARG; } @@ -173,8 +173,8 @@ static esp_err_t __ws1280_show(rmt_symbol_word_t *symbols, uint16_t sym_count) } rmt_transmit_config_t tx_cfg = { - .loop_count = 0, // Transmit once - .flags.eot_level = 0, // Keep low level after transmission ends + .loop_count = 0, // Transmit once + .flags.eot_level = 0, // Keep low level after transmission ends }; // New transmission API: handles timing and encoding flow @@ -226,7 +226,7 @@ static void __ws1280_deinit(void) */ static OPERATE_RET __tdd_led_ws1280_open(TDD_LED_HANDLE_T dev) { - if(NULL == dev) { + if (NULL == dev) { return OPRT_INVALID_PARM; } @@ -250,13 +250,13 @@ static OPERATE_RET __tdd_led_ws1280_set(TDD_LED_HANDLE_T dev, bool is_on) { PR_NOTICE("Setting WS1280 LEDs %s", is_on ? "ON" : "OFF"); - if(NULL == dev) { + if (NULL == dev) { return OPRT_INVALID_PARM; } TDD_LED_WS1280_INFO_T *led_info = (TDD_LED_WS1280_INFO_T *)dev; - if(is_on) { + if (is_on) { __ws1280_encode_data(led_info->symbols_buf, led_info->cfg.color, led_info->cfg.led_count); } else { __ws1280_encode_data(led_info->symbols_buf, 0, led_info->cfg.led_count); @@ -279,7 +279,7 @@ static OPERATE_RET __tdd_led_ws1280_set(TDD_LED_HANDLE_T dev, bool is_on) */ static OPERATE_RET __tdd_led_ws1280_close(TDD_LED_HANDLE_T dev) { - if(NULL == dev) { + if (NULL == dev) { return OPRT_INVALID_PARM; } @@ -296,31 +296,30 @@ static OPERATE_RET __tdd_led_ws1280_close(TDD_LED_HANDLE_T dev) */ OPERATE_RET tdd_led_esp_ws1280_register(char *dev_name, TDD_LED_WS1280_CFG_T *cfg) { - TDD_LED_WS1280_INFO_T *led_info = NULL; - uint16_t symbol_count = 0; - uint32_t symbols_buf_size = 0; + TDD_LED_WS1280_INFO_T *led_info = NULL; + uint16_t symbol_count = 0; + uint32_t symbols_buf_size = 0; if (dev_name == NULL || cfg == NULL) { return OPRT_INVALID_PARM; } - if(cfg->led_count > TDD_LED_WS1280_COUNT_MAX) { + if (cfg->led_count > TDD_LED_WS1280_COUNT_MAX) { PR_ERR("LED count exceeds maximum (%d)", TDD_LED_WS1280_COUNT_MAX); return OPRT_INVALID_PARM; } - symbol_count = cfg->led_count * WS1280_BITS_PER_LED + 1; // 24-bit symbols per LED + reset timing + symbol_count = cfg->led_count * WS1280_BITS_PER_LED + 1; // 24-bit symbols per LED + reset timing symbols_buf_size = symbol_count * sizeof(rmt_symbol_word_t); - led_info = (TDD_LED_WS1280_INFO_T *)tkl_system_malloc(sizeof(TDD_LED_WS1280_INFO_T)+\ - symbols_buf_size); + led_info = (TDD_LED_WS1280_INFO_T *)tkl_system_malloc(sizeof(TDD_LED_WS1280_INFO_T) + symbols_buf_size); if (led_info == NULL) { return OPRT_MALLOC_FAILED; } memset(led_info, 0, sizeof(TDD_LED_WS1280_INFO_T) + symbols_buf_size); memcpy(&led_info->cfg, cfg, sizeof(TDD_LED_WS1280_CFG_T)); - led_info->symbols_buf = (rmt_symbol_word_t *)(led_info + 1); + led_info->symbols_buf = (rmt_symbol_word_t *)(led_info + 1); led_info->symbol_count = symbol_count; TDD_LED_INTFS_T intfs = { diff --git a/boards/ESP32/common/led/tdd_led_esp_ws1280.h b/boards/ESP32/common/led/tdd_led_esp_ws1280.h index 1b7f5b4a8..2ec72daf6 100644 --- a/boards/ESP32/common/led/tdd_led_esp_ws1280.h +++ b/boards/ESP32/common/led/tdd_led_esp_ws1280.h @@ -19,16 +19,16 @@ extern "C" { /*********************************************************** ************************macro define************************ ***********************************************************/ -#define TDD_LED_WS1280_COUNT_MAX 8 +#define TDD_LED_WS1280_COUNT_MAX 8 /*********************************************************** ***********************typedef define*********************** ***********************************************************/ typedef struct { - uint16_t led_count; - uint32_t gpio; - uint32_t color; // 24-bit color rgb/grb/.. format, depends on the LED strip -}TDD_LED_WS1280_CFG_T; + uint16_t led_count; + uint32_t gpio; + uint32_t color; // 24-bit color rgb/grb/.. format, depends on the LED strip +} TDD_LED_WS1280_CFG_T; /*********************************************************** ********************function declaration******************** diff --git a/examples/peripherals/led/src/example_led.c b/examples/peripherals/led/src/example_led.c old mode 100755 new mode 100644 index 8e92e6ca7..8b6894f85 --- a/examples/peripherals/led/src/example_led.c +++ b/examples/peripherals/led/src/example_led.c @@ -13,12 +13,10 @@ ************************macro define************************ ***********************************************************/ - /*********************************************************** ***********************typedef define*********************** ***********************************************************/ - /*********************************************************** ***********************variable define********************** ***********************************************************/ @@ -66,9 +64,9 @@ void user_main(void) tal_system_sleep(2000); TDL_LED_BLINK_CFG_T blink_cfg = { - .cnt = 5, - .start_stat = TDL_LED_ON, - .first_half_cycle_time = 300, + .cnt = 5, + .start_stat = TDL_LED_ON, + .first_half_cycle_time = 300, .latter_half_cycle_time = 300, }; TUYA_CALL_ERR_LOG(tdl_led_blink(sg_led_hdl, &blink_cfg)); @@ -124,9 +122,9 @@ static void tuya_app_thread(void *arg) void tuya_app_main(void) { THREAD_CFG_T thrd_param = {0}; - thrd_param.stackDepth = 1024 * 4; - thrd_param.priority = THREAD_PRIO_1; - thrd_param.thrdname = "tuya_app_main"; + thrd_param.stackDepth = 1024 * 4; + thrd_param.priority = THREAD_PRIO_1; + thrd_param.thrdname = "tuya_app_main"; tal_thread_create_and_start(&ty_app_thread, NULL, NULL, tuya_app_thread, NULL, &thrd_param); } #endif diff --git a/src/peripherals/led/tdd_led/src/tdd_led_gpio.c b/src/peripherals/led/tdd_led/src/tdd_led_gpio.c index 065ba903b..a052ce749 100644 --- a/src/peripherals/led/tdd_led/src/tdd_led_gpio.c +++ b/src/peripherals/led/tdd_led/src/tdd_led_gpio.c @@ -34,7 +34,7 @@ static OPERATE_RET __tdd_led_gpio_set(TDD_LED_HANDLE_T handle, bool is_on) { TDD_LED_GPIO_CFG_T *led_cfg = (TDD_LED_GPIO_CFG_T *)handle; - TUYA_GPIO_LEVEL_E level = 0; + TUYA_GPIO_LEVEL_E level = 0; if (NULL == handle) { return OPRT_INVALID_PARM; @@ -51,7 +51,7 @@ static OPERATE_RET __tdd_led_gpio_set(TDD_LED_HANDLE_T handle, bool is_on) static OPERATE_RET __tdd_led_gpio_open(TDD_LED_HANDLE_T handle) { - TDD_LED_GPIO_CFG_T *led_cfg = (TDD_LED_GPIO_CFG_T *)handle; + TDD_LED_GPIO_CFG_T *led_cfg = (TDD_LED_GPIO_CFG_T *)handle; TUYA_GPIO_BASE_CFG_T gpio_cfg = {0}; if (NULL == handle) { @@ -59,8 +59,8 @@ static OPERATE_RET __tdd_led_gpio_open(TDD_LED_HANDLE_T handle) } gpio_cfg.direct = TUYA_GPIO_OUTPUT; - gpio_cfg.mode = led_cfg->mode; - gpio_cfg.level = (true == led_cfg->level) ? TUYA_GPIO_LEVEL_LOW : TUYA_GPIO_LEVEL_HIGH; + gpio_cfg.mode = led_cfg->mode; + gpio_cfg.level = (true == led_cfg->level) ? TUYA_GPIO_LEVEL_LOW : TUYA_GPIO_LEVEL_HIGH; return tkl_gpio_init(led_cfg->pin, &gpio_cfg); } @@ -87,7 +87,7 @@ static OPERATE_RET __tdd_led_gpio_close(TDD_LED_HANDLE_T handle) OPERATE_RET tdd_led_gpio_register(char *dev_name, TDD_LED_GPIO_CFG_T *led_cfg) { TDD_LED_GPIO_CFG_T *tdd_led_cfg = NULL; - TDD_LED_INTFS_T intfs; + TDD_LED_INTFS_T intfs; if (NULL == dev_name || NULL == led_cfg) { return OPRT_INVALID_PARM; @@ -98,8 +98,8 @@ OPERATE_RET tdd_led_gpio_register(char *dev_name, TDD_LED_GPIO_CFG_T *led_cfg) memcpy(tdd_led_cfg, led_cfg, sizeof(TDD_LED_GPIO_CFG_T)); memset(&intfs, 0x00, sizeof(TDD_LED_INTFS_T)); - intfs.led_open = __tdd_led_gpio_open; - intfs.led_set = __tdd_led_gpio_set; + intfs.led_open = __tdd_led_gpio_open; + intfs.led_set = __tdd_led_gpio_set; intfs.led_close = __tdd_led_gpio_close; return tdl_led_driver_register(dev_name, (TDD_LED_HANDLE_T)tdd_led_cfg, &intfs); diff --git a/src/peripherals/led/tdl_led/src/tdl_led_manage.c b/src/peripherals/led/tdl_led/src/tdl_led_manage.c index aa46aded5..970d2bb28 100644 --- a/src/peripherals/led/tdl_led/src/tdl_led_manage.c +++ b/src/peripherals/led/tdl_led/src/tdl_led_manage.c @@ -29,20 +29,20 @@ typedef enum { typedef struct { struct tuya_list_head node; - char name[LED_DEV_NAME_MAX_LEN + 1]; - MUTEX_HANDLE mutex; + char name[LED_DEV_NAME_MAX_LEN + 1]; + MUTEX_HANDLE mutex; TDD_LED_HANDLE_T *drv_hdl; - TDD_LED_INTFS_T drv_intfs; + TDD_LED_INTFS_T drv_intfs; TDL_LED_MODE_E mode; - bool is_open; - bool is_on; + bool is_open; + bool is_on; TIMER_ID led_tm; - LED_BLINK_STAT_E blink_stat; - uint32_t blink_cnt; + LED_BLINK_STAT_E blink_stat; + uint32_t blink_cnt; TDL_LED_BLINK_CFG_T blink_cfg; } LED_DEV_INFO_T; @@ -56,8 +56,8 @@ static struct tuya_list_head sg_led_list = LIST_HEAD_INIT(sg_led_list); ***********************************************************/ static LED_DEV_INFO_T *__find_led_device(char *name) { - LED_DEV_INFO_T *led_dev = NULL; - struct tuya_list_head *pos = NULL; + LED_DEV_INFO_T *led_dev = NULL; + struct tuya_list_head *pos = NULL; if (NULL == name) { return NULL; @@ -76,8 +76,8 @@ static LED_DEV_INFO_T *__find_led_device(char *name) static OPERATE_RET __led_set_status(LED_DEV_INFO_T *led_dev, TDL_LED_STATUS_E status) { - OPERATE_RET rt = OPRT_OK; - bool is_on = false; + OPERATE_RET rt = OPRT_OK; + bool is_on = false; if (NULL == led_dev) { return OPRT_INVALID_PARM; @@ -114,12 +114,12 @@ static void __led_blink_handle(LED_DEV_INFO_T *led_dev) case LED_BLINK_START: __led_set_status(led_dev, led_dev->blink_cfg.start_stat); led_dev->blink_stat = LED_BLINK_FIRST; - nxt_time = led_dev->blink_cfg.first_half_cycle_time; + nxt_time = led_dev->blink_cfg.first_half_cycle_time; break; case LED_BLINK_FIRST: __led_set_status(led_dev, TDL_LED_TOGGLE); led_dev->blink_stat = LED_BLINK_LATTER; - nxt_time = led_dev->blink_cfg.latter_half_cycle_time; + nxt_time = led_dev->blink_cfg.latter_half_cycle_time; break; case LED_BLINK_LATTER: if (led_dev->blink_cnt > 0 && led_dev->blink_cnt != TDL_BLINK_FOREVER) { @@ -129,11 +129,11 @@ static void __led_blink_handle(LED_DEV_INFO_T *led_dev) if (0 == led_dev->blink_cnt) { __led_set_status(led_dev, led_dev->blink_cfg.end_stat); led_dev->blink_stat = LED_BLINK_IDLE; - nxt_time = 0; + nxt_time = 0; } else { __led_set_status(led_dev, TDL_LED_TOGGLE); led_dev->blink_stat = LED_BLINK_FIRST; - nxt_time = led_dev->blink_cfg.first_half_cycle_time; + nxt_time = led_dev->blink_cfg.first_half_cycle_time; } break; default: @@ -172,7 +172,7 @@ static void __led_stop_blink(LED_DEV_INFO_T *led_dev) tal_sw_timer_stop(led_dev->led_tm); } led_dev->blink_stat = LED_BLINK_IDLE; - led_dev->blink_cnt = 0; + led_dev->blink_cnt = 0; } /** @@ -203,7 +203,7 @@ TDL_LED_HANDLE_T tdl_led_find_dev(char *dev_name) */ OPERATE_RET tdl_led_open(TDL_LED_HANDLE_T handle) { - OPERATE_RET rt = OPRT_OK; + OPERATE_RET rt = OPRT_OK; LED_DEV_INFO_T *led_dev = NULL; if (NULL == handle) { @@ -279,7 +279,7 @@ OPERATE_RET tdl_led_set_status(TDL_LED_HANDLE_T handle, TDL_LED_STATUS_E status) */ OPERATE_RET tdl_led_flash(TDL_LED_HANDLE_T handle, uint32_t half_cycle_time) { - OPERATE_RET rt = OPRT_OK; + OPERATE_RET rt = OPRT_OK; LED_DEV_INFO_T *led_dev = (LED_DEV_INFO_T *)handle; if (NULL == led_dev || 0 == half_cycle_time) { @@ -296,13 +296,13 @@ OPERATE_RET tdl_led_flash(TDL_LED_HANDLE_T handle, uint32_t half_cycle_time) __led_stop_blink(led_dev); - led_dev->blink_cfg.cnt = TDL_BLINK_FOREVER; - led_dev->blink_cfg.start_stat = TDL_LED_ON; - led_dev->blink_cfg.first_half_cycle_time = half_cycle_time; + led_dev->blink_cfg.cnt = TDL_BLINK_FOREVER; + led_dev->blink_cfg.start_stat = TDL_LED_ON; + led_dev->blink_cfg.first_half_cycle_time = half_cycle_time; led_dev->blink_cfg.latter_half_cycle_time = half_cycle_time; led_dev->blink_stat = LED_BLINK_START; - led_dev->blink_cnt = led_dev->blink_cfg.cnt; + led_dev->blink_cnt = led_dev->blink_cfg.cnt; rt = tal_sw_timer_start(led_dev->led_tm, 10, TAL_TIMER_ONCE); if (rt != OPRT_OK) { @@ -310,7 +310,7 @@ OPERATE_RET tdl_led_flash(TDL_LED_HANDLE_T handle, uint32_t half_cycle_time) tal_mutex_unlock(led_dev->mutex); return rt; } - + tal_mutex_unlock(led_dev->mutex); return OPRT_OK; @@ -326,7 +326,7 @@ OPERATE_RET tdl_led_flash(TDL_LED_HANDLE_T handle, uint32_t half_cycle_time) */ OPERATE_RET tdl_led_blink(TDL_LED_HANDLE_T handle, TDL_LED_BLINK_CFG_T *cfg) { - OPERATE_RET rt = OPRT_OK; + OPERATE_RET rt = OPRT_OK; LED_DEV_INFO_T *led_dev = (LED_DEV_INFO_T *)handle; if (NULL == led_dev || NULL == cfg) { @@ -346,7 +346,7 @@ OPERATE_RET tdl_led_blink(TDL_LED_HANDLE_T handle, TDL_LED_BLINK_CFG_T *cfg) memcpy(&led_dev->blink_cfg, cfg, sizeof(TDL_LED_BLINK_CFG_T)); led_dev->blink_stat = LED_BLINK_START; - led_dev->blink_cnt = led_dev->blink_cfg.cnt; + led_dev->blink_cnt = led_dev->blink_cfg.cnt; rt = tal_sw_timer_start(led_dev->led_tm, 10, TAL_TIMER_ONCE); if (rt != OPRT_OK) { @@ -369,7 +369,7 @@ OPERATE_RET tdl_led_blink(TDL_LED_HANDLE_T handle, TDL_LED_BLINK_CFG_T *cfg) */ OPERATE_RET tdl_led_close(TDL_LED_HANDLE_T handle) { - OPERATE_RET rt = OPRT_OK; + OPERATE_RET rt = OPRT_OK; LED_DEV_INFO_T *led_dev = (LED_DEV_INFO_T *)handle; if (NULL == led_dev) { diff --git a/src/tuya_ai_service/svc_ai_basic/src/tuya_ai_private.h b/src/tuya_ai_service/svc_ai_basic/src/tuya_ai_private.h index aefb64bb6..7446f1e47 100644 --- a/src/tuya_ai_service/svc_ai_basic/src/tuya_ai_private.h +++ b/src/tuya_ai_service/svc_ai_basic/src/tuya_ai_private.h @@ -26,16 +26,15 @@ #include "tal_memory.h" #include "tal_thread.h" -#if defined(AI_HEAP_IN_PSRAM) && (AI_HEAP_IN_PSRAM == 1) &&\ - defined(ENABLE_EXT_RAM) && (ENABLE_EXT_RAM == 1) -#define OS_MALLOC(size) tal_psram_malloc(size) -#define OS_FREE(ptr) tal_psram_free(ptr) -#define OS_CALLOC(num, size) tal_psram_calloc(num, size) +#if defined(AI_HEAP_IN_PSRAM) && (AI_HEAP_IN_PSRAM == 1) && defined(ENABLE_EXT_RAM) && (ENABLE_EXT_RAM == 1) +#define OS_MALLOC(size) tal_psram_malloc(size) +#define OS_FREE(ptr) tal_psram_free(ptr) +#define OS_CALLOC(num, size) tal_psram_calloc(num, size) #define OS_REALLOC(ptr, size) tal_psram_realloc(ptr, size) #else -#define OS_MALLOC(size) tal_malloc(size) -#define OS_FREE(ptr) tal_free(ptr) -#define OS_CALLOC(num, size) tal_calloc(num, size) +#define OS_MALLOC(size) tal_malloc(size) +#define OS_FREE(ptr) tal_free(ptr) +#define OS_CALLOC(num, size) tal_calloc(num, size) #define OS_REALLOC(ptr, size) tal_realloc(ptr, size) #endif