Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions claw/core/gateway.c
Original file line number Diff line number Diff line change
Expand Up @@ -38,26 +38,37 @@ int gateway_register_service(const char *name, uint8_t type_mask,
return CLAW_OK;
}

/* --- Statistics --- */

static struct gateway_stats s_stats;

/* --- Message dispatch --- */

static void dispatch_msg(struct gateway_msg *msg)
{
uint8_t bit = (uint8_t)(1 << msg->type);
int delivered = 0;

s_stats.total++;
if (msg->type < GW_MSG_TYPE_MAX) {
s_stats.per_type[msg->type]++;
}

for (int i = 0; i < s_service_count; i++) {
if (s_services[i].type_mask & bit) {
if (claw_mq_send(s_services[i].inbox, msg, sizeof(*msg),
CLAW_NO_WAIT) == CLAW_OK) {
delivered++;
} else {
s_stats.dropped++;
CLAW_LOGW(TAG, "drop msg type=%d -> %s (queue full)",
msg->type, s_services[i].name);
}
}
}

if (delivered == 0) {
s_stats.no_consumer++;
CLAW_LOGD(TAG, "msg type=%d len=%d (no consumer)", msg->type,
msg->len);
}
Expand Down Expand Up @@ -85,6 +96,7 @@ static void gateway_thread_entry(void *param)
int gateway_init(void)
{
memset(s_services, 0, sizeof(s_services));
memset(&s_stats, 0, sizeof(s_stats));
s_service_count = 0;

gw_mq = claw_mq_create("gw_mq", sizeof(struct gateway_msg),
Expand Down Expand Up @@ -114,3 +126,10 @@ int gateway_send(struct gateway_msg *msg)
{
return claw_mq_send(gw_mq, msg, sizeof(*msg), CLAW_NO_WAIT);
}

void gateway_get_stats(struct gateway_stats *out)
{
if (out) {
memcpy(out, &s_stats, sizeof(s_stats));
}
}
49 changes: 49 additions & 0 deletions claw/core/heartbeat.c
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@
#include <string.h>
#include <stdio.h>

#ifdef CLAW_PLATFORM_ESP_IDF
#include "esp_heap_caps.h"
#elif defined(CLAW_PLATFORM_RTTHREAD)
#include <rtthread.h>
#endif

#define TAG "heartbeat"

/* LLM connectivity state: -1 = unknown, 0 = offline, 1 = online */
Expand Down Expand Up @@ -129,10 +135,53 @@ static void heartbeat_ai_thread(void *arg)
claw_mutex_unlock(s_lock);
}

/*
* Device health check — collect anomalies and post events.
* Runs before the LLM ping so anomalies are included in the
* next heartbeat AI summary.
*/
static void check_device_health(void)
{
uint32_t free_kb = 0;
uint32_t total_kb = 0;

#ifdef CLAW_PLATFORM_ESP_IDF
size_t free_sz = heap_caps_get_free_size(MALLOC_CAP_DEFAULT);
size_t total_sz = heap_caps_get_total_size(MALLOC_CAP_DEFAULT);
free_kb = (uint32_t)(free_sz / 1024);
total_kb = (uint32_t)(total_sz / 1024);
#elif defined(CLAW_PLATFORM_RTTHREAD)
rt_size_t total_rt = 0, used_rt = 0, max_rt = 0;
rt_memory_info(&total_rt, &used_rt, &max_rt);
free_kb = (uint32_t)((total_rt - used_rt) / 1024);
total_kb = (uint32_t)(total_rt / 1024);
#endif

if (total_kb == 0) {
return;
}

uint32_t used_pct = 100 - (free_kb * 100 / total_kb);

/* Alert when heap usage exceeds 80% */
if (used_pct > 80) {
char msg[80];
snprintf(msg, sizeof(msg),
"heap %lu%% used (%luKB free / %luKB total)",
(unsigned long)used_pct,
(unsigned long)free_kb,
(unsigned long)total_kb);
heartbeat_post("memory", msg);
}
}

/* LLM connectivity probe — runs in its own thread */
static void ping_thread(void *arg)
{
(void)arg;

check_device_health();

int ok = (ai_ping() == CLAW_OK) ? 1 : 0;
int prev = s_llm_state;
s_llm_state = ok;
Expand Down
38 changes: 36 additions & 2 deletions claw/shell/shell_commands.c
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,17 @@ void shell_nvs_config_load(void)

/* ---- Common command handlers ---- */

static const char *log_level_name(int level)
{
switch (level) {
case CLAW_LOG_ERROR: return "error";
case CLAW_LOG_WARN: return "warn";
case CLAW_LOG_INFO: return "info";
case CLAW_LOG_DEBUG: return "debug";
default: return "unknown";
}
}

static void cmd_log(int argc, char **argv)
{
if (argc < 2) {
Expand All @@ -100,8 +111,31 @@ static void cmd_log(int argc, char **argv)
claw_log_set_enabled(1);
} else if (strcmp(argv[1], "off") == 0) {
claw_log_set_enabled(0);
} else if (strcmp(argv[1], "level") == 0) {
if (argc < 3) {
printf("Log level: %s\n",
log_level_name(claw_log_get_level()));
return;
}
int lv = -1;
if (strcmp(argv[2], "error") == 0) {
lv = CLAW_LOG_ERROR;
} else if (strcmp(argv[2], "warn") == 0) {
lv = CLAW_LOG_WARN;
} else if (strcmp(argv[2], "info") == 0) {
lv = CLAW_LOG_INFO;
} else if (strcmp(argv[2], "debug") == 0) {
lv = CLAW_LOG_DEBUG;
}
if (lv < 0) {
printf("Usage: /log level <error|warn|info|debug>\n");
return;
}
claw_log_set_level(lv);
printf("Log level: %s\n", log_level_name(lv));
return;
} else {
printf("Usage: /log [on|off]\n");
printf("Usage: /log [on|off|level <error|warn|info|debug>]\n");
return;
}
printf("Log output: %s\n",
Expand Down Expand Up @@ -305,7 +339,7 @@ static void cmd_nodes(int argc, char **argv)
/* ---- Common command table ---- */

const shell_cmd_t shell_common_commands[] = {
SHELL_CMD("/log", cmd_log, "Toggle log [on|off]"),
SHELL_CMD("/log", cmd_log, "Log [on|off|level <lvl>]"),
SHELL_CMD("/history", cmd_history, "Show conversation count"),
SHELL_CMD("/clear", cmd_clear, "Clear conversation memory"),
SHELL_CMD("/ai_set", cmd_ai_set, "Set AI config (NVS)"),
Expand Down
12 changes: 10 additions & 2 deletions claw/tools/tool_system.c
Original file line number Diff line number Diff line change
Expand Up @@ -177,14 +177,22 @@ static int tool_memory_info(const cJSON *params, cJSON *result)
size_t total = heap_caps_get_total_size(MALLOC_CAP_DEFAULT);
size_t free_sz = heap_caps_get_free_size(MALLOC_CAP_DEFAULT);
size_t min_free = heap_caps_get_minimum_free_size(MALLOC_CAP_DEFAULT);
size_t largest = heap_caps_get_largest_free_block(MALLOC_CAP_DEFAULT);

cJSON_AddStringToObject(result, "status", "ok");
cJSON_AddNumberToObject(result, "heap_total_bytes", total);
cJSON_AddNumberToObject(result, "heap_free_bytes", free_sz);
cJSON_AddNumberToObject(result, "heap_min_free_bytes", min_free);
cJSON_AddNumberToObject(result, "heap_largest_block", largest);
cJSON_AddNumberToObject(result, "heap_used_percent",
(double)(total - free_sz) / total * 100.0);

/* fragmentation: 100% = all fragments, 0% = single block */
if (free_sz > 0) {
double frag = (1.0 - (double)largest / free_sz) * 100.0;
cJSON_AddNumberToObject(result, "fragmentation_percent", frag);
}

return CLAW_OK;
}

Expand Down Expand Up @@ -231,8 +239,8 @@ void claw_tools_register_system(void)
0, CLAW_TOOL_LOCAL_ONLY);

claw_tool_register("memory_info",
"Get heap memory status: total, free, minimum-ever-free bytes, "
"and usage percentage.",
"Get heap memory status: total, free, minimum-ever-free, "
"largest contiguous block, usage and fragmentation percent.",
schema_empty, tool_memory_info,
0, CLAW_TOOL_LOCAL_ONLY);

Expand Down
12 changes: 8 additions & 4 deletions docs/en/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,8 @@ Message routing hub with service registry and type-based dispatch. Services
register with a type_mask bitmap and their own message queue; the gateway
delivers incoming messages to all matching consumers. Message types: DATA,
CMD, EVENT, SWARM, AI_REQ. Queue: 16 messages x 256 bytes. Dedicated thread
at priority 15.
at priority 15. Built-in dispatch statistics track total/per-type/dropped/
no-consumer message counts via `gateway_get_stats()`.

### Scheduler (`claw/core/scheduler.c`)

Expand All @@ -78,9 +79,12 @@ executed in turn, preventing task starvation.

### Heartbeat (`claw/core/heartbeat.c`)

Optional periodic AI check-in every 5 minutes. Sends a heartbeat prompt
to the AI engine so the assistant can perform background monitoring.
Depends on the scheduler service for timing.
Optional periodic AI check-in every 5 minutes. Three-layer tick logic:
(1) events pending -- AI summary via `ai_chat_raw()`;
(2) no events -- device health check (heap > 80% triggers auto-alert)
followed by lightweight LLM connectivity ping (`ai_ping()`,
max_tokens=1); (3) state change (online/offline) -- notification
delivered to IM or console. Depends on the scheduler service for timing.

### AI Engine (`claw/services/ai/`)

Expand Down
3 changes: 2 additions & 1 deletion docs/en/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ Commands start with `/`.
| `/skill <name> [args]` | Execute skill |
| `/nodes` | Show swarm nodes |
| `/log [on\|off]` | Toggle log output |
| `/log level <lvl>` | Set log level (error/warn/info/debug) |
| `/history` | Show message count |
| `/clear` | Clear conversation |
| `/help` | List commands |
Expand Down Expand Up @@ -73,7 +74,7 @@ is always enabled).
| Tool | Parameters | Description |
|------|------------|-------------|
| `system_info` | — | Show system information |
| `memory_info` | — | Show heap usage |
| `memory_info` | — | Show heap usage, largest block, fragmentation |
| `clear_history` | — | Clear conversation history |
| `system_restart` | — | Reboot the device |
| `save_memory` | `key`, `value` | Save to long-term memory |
Expand Down
9 changes: 7 additions & 2 deletions docs/zh/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ claw/*.c ---> #include "osal/claw_os.h"
和自有消息队列注册;网关将传入消息投递到所有匹配的消费者。
消息类型:DATA、CMD、EVENT、SWARM、AI_REQ。
队列容量:16 条消息 x 256 字节。专用线程优先级为 15。
内置消息统计:通过 `gateway_get_stats()` 查询总计/按类型/丢弃/无消费者计数。

### 调度器(`claw/core/scheduler.c`)

Expand All @@ -75,8 +76,12 @@ claw/*.c ---> #include "osal/claw_os.h"

### 心跳(`claw/core/heartbeat.c`)

可选的周期性 AI 签到功能,每 5 分钟执行一次。向 AI 引擎发送心跳提示,
使助手能够执行后台监控。依赖调度器服务进行计时。
可选的周期性 AI 签到功能,每 5 分钟执行一次。三层 tick 逻辑:
(1)有待处理事件——通过 `ai_chat_raw()` 进行 AI 总结;
(2)无事件——设备健康检查(堆使用 > 80% 触发自动告警),
随后进行轻量级 LLM 连通性探测(`ai_ping()`,max_tokens=1);
(3)状态变化(在线/离线)——通知投递到 IM 或控制台。
依赖调度器服务进行计时。

### AI 引擎(`claw/services/ai/`)

Expand Down
3 changes: 2 additions & 1 deletion docs/zh/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
| `/skill <name> [args]` | 执行技能 |
| `/nodes` | 显示集群节点 |
| `/log [on\|off]` | 切换日志输出 |
| `/log level <lvl>` | 设置日志级别(error/warn/info/debug) |
| `/history` | 显示消息数量 |
| `/clear` | 清空对话 |
| `/help` | 列出命令 |
Expand Down Expand Up @@ -72,7 +73,7 @@ Kconfig 选项控制(Audio 除外,始终启用)。
| 工具 | 参数 | 说明 |
|------|------|------|
| `system_info` | — | 显示系统信息 |
| `memory_info` | — | 显示堆内存使用情况 |
| `memory_info` | — | 显示堆内存使用、最大连续块、碎片率 |
| `clear_history` | — | 清空对话历史 |
| `system_restart` | — | 重启设备 |
| `save_memory` | `key`, `value` | 保存到长期记忆 |
Expand Down
9 changes: 9 additions & 0 deletions include/claw/core/gateway.h
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,17 @@ struct gw_service_entry {
claw_mq_t inbox; /* service's own message queue */
};

/* Message statistics */
struct gateway_stats {
uint32_t total; /* total dispatched */
uint32_t per_type[GW_MSG_TYPE_MAX]; /* per message type */
uint32_t dropped; /* queue-full drops */
uint32_t no_consumer; /* msgs with no match */
};

int gateway_init(void);
int gateway_send(struct gateway_msg *msg);
void gateway_get_stats(struct gateway_stats *out);

/**
* Register a service to receive messages matching type_mask.
Expand Down
2 changes: 2 additions & 0 deletions include/osal/claw_os.h
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,8 @@ void claw_log(int level, const char *tag, const char *fmt, ...);
void claw_log_raw(const char *fmt, ...);
void claw_log_set_enabled(int enabled);
int claw_log_get_enabled(void);
void claw_log_set_level(int level);
int claw_log_get_level(void);

/* Convenience macros */
#define CLAW_LOGE(tag, fmt, ...) claw_log(CLAW_LOG_ERROR, tag, fmt, ##__VA_ARGS__)
Expand Down
27 changes: 25 additions & 2 deletions osal/freertos/claw_os_freertos.c
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,7 @@ void claw_free(void *ptr)
/* ---------- Log ---------- */

static int s_log_enabled = 1;
static int s_log_level = CLAW_LOG_DEBUG; /* show all by default */

void claw_log_set_enabled(int enabled)
{
Expand All @@ -285,11 +286,33 @@ int claw_log_get_enabled(void)
return s_log_enabled;
}

void claw_log_set_level(int level)
{
if (level < CLAW_LOG_ERROR) {
level = CLAW_LOG_ERROR;
}
if (level > CLAW_LOG_DEBUG) {
level = CLAW_LOG_DEBUG;
}
s_log_level = level;
#ifdef CLAW_PLATFORM_ESP_IDF
static const esp_log_level_t map[] = {
ESP_LOG_ERROR, ESP_LOG_WARN, ESP_LOG_INFO, ESP_LOG_DEBUG
};
esp_log_level_set("*", map[level]);
#endif
}

int claw_log_get_level(void)
{
return s_log_level;
}

#ifdef CLAW_PLATFORM_ESP_IDF

void claw_log(int level, const char *tag, const char *fmt, ...)
{
if (!s_log_enabled) {
if (!s_log_enabled || level > s_log_level) {
return;
}

Expand Down Expand Up @@ -346,7 +369,7 @@ void claw_log(int level, const char *tag, const char *fmt, ...)
{
va_list ap;

if (!s_log_enabled) {
if (!s_log_enabled || level > s_log_level) {
return;
}
if (level < 0) {
Expand Down
Loading
Loading