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