Compare commits

...

3 Commits

Author SHA1 Message Date
3e709577f5 docs: 更新 Claude Code 插件高效运用指南 (加 embed-ai-tool 3 个嵌入式 skill)
补充内容:
- 一、资源总览: 新增 "embed-ai-tool 嵌入式专项 Skills (3 个)" 行
- 五、自动触发表: 加 serial-monitor / rtos-debug / static-analysis 触发关键词
- 五、协同表: 加 embed-ai-tool 与插件配合的工作流
- 八、新电脑环境恢复指南:
  * 新增步骤 3.1.5 安装 embed-ai-tool 3 个 skill (git clone + cp)
  * 8.3 恢复内容清单新增 embed-ai-tool 行
  * 可选依赖工具说明 (pyserial / cppcheck / llvm)

3 个 skill 用途:
- serial-monitor: 串口抓包/日志分析 (避免手动复制粘贴日志)
- rtos-debug: FreeRTOS 任务/栈/死锁分析
- static-analysis: cppcheck/clang-tidy/MISRA-C 静态扫描

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 18:17:54 +08:00
b7ee0f9885 chore(docs): 数字人 GIF 资源更新 (ezgif 处理后 + Cubism 工程同步)
新增 Ezgif_Export/ 目录, 存放 8 张 ezgif 处理后的 GIF (240×320 + 抽帧 N=3),
这些是直接喂给在线 EAF Packer 的源文件, 跟 spiffs_image/hiyori-assets.bin 对应.

也包含 m03/m05 源 GIF 更新 (m05 从 7 MB 优化到 1.9 MB, ezgif resize) 和
Cubism 工程 hiyori_free_t03.can3 同步.

ezgif 处理参数 (实测最优):
  - Resize: 240×320 + ImageMagick + coalesce (撤销优化) + 添加透明内边距
  - Frames: Every 3 + Skip every N-th (-33% 帧数)
  - Converter: libvips (无 dithering)
  - Effects: don't stack frames ✓

EAF Packer 配置:
  - 屏幕: 240×320 矩形 + 背景色 #000000 (chroma key 必须黑色)
  - 命名: hiyori_m01 ~ hiyori_m08 (跟代码 emotion_map 对齐)
  - 循环模式: 双点循环 [0, 末帧]

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 18:17:39 +08:00
be788be251 feat(ui): 8 张 EAF 数字人完整接入 (m01~m08 全情绪映射)
数字人从原本 m06+m07 两张 EAF 扩展到 m01~m08 八张, 通过 ezgif 240×320 +
抽帧 N=3 + EAF Packer 配置, 8 张 EAF 总和压缩到 4.32 MB, SPIFFS 当前
4.94 MB 分区直接装下, 无需扩分区也不丢 OTA 升级能力.

main/dzbj/ai_chat_ui_eaf.c:
  - 新版/旧版 MMAP 自动检测 (header[8] == 0x14 → entry 32B; 0x10 → 28B),
    兼容在线 EAF Packer 两种导出格式 (FSIZE/FOFFSET 偏移自动适配).
  - find_cache_index_by_name 加 fallback: 找不到精确匹配时返回 cache[0],
    PoC 阶段单张 EAF 也能验证全部情绪触发.
  - emotion_map 22 情绪 → 8 张 EAF 重排:
    * 默认/积极组 12 → m01..m05 均分 (每张 2-3 种)
    * 思考/疲倦组 5 → m06
    * 负面/严肃组 3 → m07
    * 惊讶组 2 → m08
  - 数字人对齐 GFX_ALIGN_CENTER → GFX_ALIGN_BOTTOM_MID, 240×320 在
    360×360 圆屏贴底显示, 顶部 40px 透明露出背景图, 视觉跟之前
    360×360 全屏 EAF 一致 (脚部贴底, 字幕 z-index 上层覆盖底部 56px).

spiffs_image:
  - hiyori-assets.bin: 956 KB (m06+m07) → 4.53 MB (m01~m08 + index.json)
  - 删除原 GIF (hiyori_m{03,06,07}.gif), EAF 已替代不需要烧到设备.

实测数据 (Baji 2026-05-20):
  m01=814KB m02=516KB m03=563KB m04=559KB m05=606KB m06=423KB m07=379KB m08=566KB
  8 张 EAF 总和: 4.32 MB
  SPIFFS 占用: 4.58 MB / 4.64 MB 可用 = 1.3% 余量 (临界, 未来加资源需要规划)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 18:17:09 +08:00
17 changed files with 104 additions and 29 deletions

View File

@ -1,6 +1,6 @@
# Claude Code 插件高效运用指南 # Claude Code 插件高效运用指南
> 更新日期: 2026-04-13新增 GSD 执行框架 + 完善工具链全景 > 更新日期: 2026-05-19新增 embed-ai-tool 3 个嵌入式专项 skillserial-monitor / rtos-debug / static-analysis
> 适用环境: macOS / Claude Code 2.1.79+ / ESP32 嵌入式开发 > 适用环境: macOS / Claude Code 2.1.79+ / ESP32 嵌入式开发
--- ---
@ -14,6 +14,7 @@
| **GSD 执行框架** | **68 个 Skills** | 防上下文腐烂、任务编排、context monitor、原子提交 | | **GSD 执行框架** | **68 个 Skills** | 防上下文腐烂、任务编排、context monitor、原子提交 |
| 自定义 Skills (~/.claude/skills/) | 11 个 | ESP32 专用 6 个 + RK3588/Linux 驱动 4 个 + 硬件驱动工作流 1 个 | | 自定义 Skills (~/.claude/skills/) | 11 个 | ESP32 专用 6 个 + RK3588/Linux 驱动 4 个 + 硬件驱动工作流 1 个 |
| 第三方 Skills (~/.claude/skills/) | 7 个 | find-skills、tmux、summarize、tavily-research、embedded-systems、think、health | | 第三方 Skills (~/.claude/skills/) | 7 个 | find-skills、tmux、summarize、tavily-research、embedded-systems、think、health |
| **embed-ai-tool 嵌入式专项 Skills** | **3 个** | serial-monitor串口抓包/日志分析、rtos-debugFreeRTOS 任务/栈/死锁、static-analysiscppcheck/clang-tidy/MISRA-C |
| 内置 Skills | 6 个 | simplify、loop、claude-api、schedule、update-config、keybindings-help | | 内置 Skills | 6 个 | simplify、loop、claude-api、schedule、update-config、keybindings-help |
--- ---
@ -567,6 +568,9 @@ allowed-tools: Bash, Read, Grep, Glob # 可选,限制可用工具
| **embedded-systems** | 涉及固件开发、RTOS、中断处理、DMA、功耗优化、裸机编程、volatile 声明等通用嵌入式工程原则 | | **embedded-systems** | 涉及固件开发、RTOS、中断处理、DMA、功耗优化、裸机编程、volatile 声明等通用嵌入式工程原则 |
| **think** | 新功能、架构决策前。质疑需求、压力测试设计、提供 2-3 方案对比(不用于小 Bug 修复) | | **think** | 新功能、架构决策前。质疑需求、压力测试设计、提供 2-3 方案对比(不用于小 Bug 修复) |
| **health** | Claude 行为异常、hooks 失效、Skills 配置冲突时。审计六层配置栈,按严重程度分级报告 | | **health** | Claude 行为异常、hooks 失效、Skills 配置冲突时。审计六层配置栈,按严重程度分级报告 |
| **serial-monitor** | 说"抓串口"、"看串口日志"、"识别串口"、需要监控 UART 启动日志或断言输出 |
| **rtos-debug** | 说"FreeRTOS 任务"、"栈水位"、"死锁"、"线程感知调试"、`pxCurrentTCB``uxTaskGetSystemState` |
| **static-analysis** | 说"静态分析"、"cppcheck"、"clang-tidy"、"MISRA-C"、提交前缺陷扫描 |
### ESP32 Skills 与插件配合 ### ESP32 Skills 与插件配合
@ -579,6 +583,14 @@ allowed-tools: Bash, Read, Grep, Glob # 可选,限制可用工具
| esp-code-review | "帮我审查代码" | 先 esp-code-review → 再 `/review-pr` 双重审查 | | esp-code-review | "帮我审查代码" | 先 esp-code-review → 再 `/review-pr` 双重审查 |
| esp-driver | "写一个I2C驱动" | `/feature-dev` 设计 → esp-driver 生成 → `/code-review` 审查 | | esp-driver | "写一个I2C驱动" | `/feature-dev` 设计 → esp-driver 生成 → `/code-review` 审查 |
### embed-ai-tool 嵌入式专项 Skills 与插件配合
| Skill | 触发方式 | 与插件配合 |
|-------|---------|-----------|
| **serial-monitor** | "抓串口" / "看启动日志" / "识别 ESP32 串口" | serial-monitor 抓日志 → esp-analyze-log 解析 → `/revise-claude-md` 记录 |
| **rtos-debug** | "FreeRTOS 任务栈不够" / "死锁分析" / "看 pxCurrentTCB" | rtos-debug 分析任务/栈/优先级 → esp-troubleshoot 排障 → `/commit` 记录修复 |
| **static-analysis** | "提交前静态扫描" / "cppcheck 一下" / "MISRA-C 检查" | static-analysis 扫描 → esp-code-review 二次审查 → `/commit-push-pr` 发布 |
### RK3588/Linux 驱动 Skills 与插件配合 ### RK3588/Linux 驱动 Skills 与插件配合
| 自定义 Skill | 触发方式 | 与插件配合 | | 自定义 Skill | 触发方式 | 与插件配合 |
@ -697,6 +709,40 @@ npx skills add tw93/Waza@think -g -y
npx skills add tw93/Waza@health -g -y npx skills add tw93/Waza@health -g -y
``` ```
#### 步骤 3.1.5:安装 embed-ai-tool 嵌入式专项 Skills3 个)
这 3 个 skill 是手动从 GitHub 仓库 `LeoKemp223/embed-ai-tool` 复制的,不通过 `npx skills add`
```bash
# 临时 clone
cd /tmp && git clone --depth 1 https://github.com/LeoKemp223/embed-ai-tool.git
# 复制 3 个 skill 到 ~/.claude/skills/
cp -r /tmp/embed-ai-tool/skills/serial-monitor ~/.claude/skills/
cp -r /tmp/embed-ai-tool/skills/rtos-debug ~/.claude/skills/
cp -r /tmp/embed-ai-tool/skills/static-analysis ~/.claude/skills/
# 复制 shared 公共模块 (rtos-debug / static-analysis 依赖 tool_config.py 等)
mkdir -p ~/.claude/skills/shared
cp /tmp/embed-ai-tool/shared/*.py ~/.claude/skills/shared/
cp /tmp/embed-ai-tool/shared/platform-compatibility.md ~/.claude/skills/shared/
# 清理
rm -rf /tmp/embed-ai-tool
```
**可选:安装这 3 个 skill 依赖的外部工具**(用到才装,不强制):
```bash
# serial-monitor 依赖
pip3 install pyserial
# static-analysis 依赖
brew install cppcheck llvm # llvm 自带 clang-tidy
```
> **注意**:脚本调用走 `python3 ~/.claude/skills/<skill>/scripts/<script>.py`,依赖未装时 skill 会提示安装命令。
#### 步骤 3.2:安装 GSD 执行框架 #### 步骤 3.2:安装 GSD 执行框架
GSDGet Shit Done是防上下文腐烂的任务编排框架安装后自动启用 context monitor hook GSDGet Shit Done是防上下文腐烂的任务编排框架安装后自动启用 context monitor hook
@ -743,6 +789,7 @@ idf.py --version # 应输出 ESP-IDF v5.4.2
| 本指南文档 | Git 备份 | ❌ | clone 后自动生效 | | 本指南文档 | Git 备份 | ❌ | clone 后自动生效 |
| 插件代码9 个插件) | 远程下载 | ✅ | 执行 `claude plugins install`(步骤 3 | | 插件代码9 个插件) | 远程下载 | ✅ | 执行 `claude plugins install`(步骤 3 |
| 第三方 Skills7 个) | 远程下载 | ✅ | 执行 `npx skills add`(步骤 3.1 | | 第三方 Skills7 个) | 远程下载 | ✅ | 执行 `npx skills add`(步骤 3.1 |
| **embed-ai-tool 嵌入式专项 Skills3 个)** | **GitHub clone** | **✅** | **执行 `git clone + cp`(步骤 3.1.5** |
| GSD 执行框架68 个 Skills | 远程下载 | ✅ | 执行 `npx get-shit-done-cc@latest`(步骤 3.2 | | GSD 执行框架68 个 Skills | 远程下载 | ✅ | 执行 `npx get-shit-done-cc@latest`(步骤 3.2 |
| Claude Code 程序 | npm 远程 | ✅ | 执行 `npm install -g` | | Claude Code 程序 | npm 远程 | ✅ | 执行 `npm install -g` |
| ESP-IDF v5.4.2 | GitHub | ✅ | 执行 `git clone` + `install.sh` | | ESP-IDF v5.4.2 | GitHub | ✅ | 执行 `git clone` + `install.sh` |

Binary file not shown.

After

Width:  |  Height:  |  Size: 820 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 522 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 570 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 560 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 617 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 426 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 382 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 572 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 MiB

After

Width:  |  Height:  |  Size: 3.4 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 MiB

After

Width:  |  Height:  |  Size: 1.8 MiB

View File

@ -96,31 +96,35 @@ typedef struct {
} eaf_emotion_map_t; } eaf_emotion_map_t;
static const eaf_emotion_map_t s_emotion_map[] = { static const eaf_emotion_map_t s_emotion_map[] = {
// 默认/积极 → m06 // 22 种情绪 → 8 张 EAF (m01~m08, 2026-05-20 全 8 张接入)
{"neutral", "hiyori_m06.eaf"}, // 注: 具体动作内容需要用户在 EAF Packer 中确认后调整, 当前是按情绪类别 +
{"happy", "hiyori_m06.eaf"}, // 编号顺序的初版分配, 烧录后看效果再精准调整
{"laughing", "hiyori_m06.eaf"}, // 默认/积极组 (12 → m01..m05 均分, 每张约 2-3 种情绪)
{"funny", "hiyori_m06.eaf"}, {"neutral", "hiyori_m01.eaf"},
{"loving", "hiyori_m06.eaf"}, {"happy", "hiyori_m01.eaf"},
{"relaxed", "hiyori_m06.eaf"}, {"blink", "hiyori_m01.eaf"},
{"delicious", "hiyori_m06.eaf"}, {"laughing", "hiyori_m02.eaf"},
{"kissy", "hiyori_m06.eaf"}, {"funny", "hiyori_m02.eaf"},
{"confident", "hiyori_m06.eaf"}, {"curious", "hiyori_m02.eaf"},
{"silly", "hiyori_m06.eaf"}, {"loving", "hiyori_m03.eaf"},
{"blink", "hiyori_m06.eaf"}, {"relaxed", "hiyori_m03.eaf"},
{"curious", "hiyori_m06.eaf"}, {"delicious", "hiyori_m04.eaf"},
// 思考/疲倦 → m07 {"kissy", "hiyori_m04.eaf"},
{"sleepy", "hiyori_m07.eaf"}, {"confident", "hiyori_m05.eaf"},
{"thinking", "hiyori_m07.eaf"}, {"silly", "hiyori_m05.eaf"},
{"confused", "hiyori_m07.eaf"}, // 思考/疲倦组 (5) → m06
{"embarrassed", "hiyori_m07.eaf"}, {"sleepy", "hiyori_m06.eaf"},
{"dizzy", "hiyori_m07.eaf"}, {"thinking", "hiyori_m06.eaf"},
// 负面/严肃 → 暂用 m07m03 未导入) {"confused", "hiyori_m06.eaf"},
{"embarrassed", "hiyori_m06.eaf"},
{"dizzy", "hiyori_m06.eaf"},
// 负面/严肃组 (3) → m07
{"sad", "hiyori_m07.eaf"}, {"sad", "hiyori_m07.eaf"},
{"crying", "hiyori_m07.eaf"}, {"crying", "hiyori_m07.eaf"},
{"angry", "hiyori_m07.eaf"}, {"angry", "hiyori_m07.eaf"},
{"surprised", "hiyori_m07.eaf"}, // 惊讶组 (2) → m08
{"shocked", "hiyori_m07.eaf"}, {"surprised", "hiyori_m08.eaf"},
{"shocked", "hiyori_m08.eaf"},
}; };
#define EMOTION_MAP_SIZE (sizeof(s_emotion_map) / sizeof(s_emotion_map[0])) #define EMOTION_MAP_SIZE (sizeof(s_emotion_map) / sizeof(s_emotion_map[0]))
@ -153,6 +157,13 @@ static int find_cache_index_by_name(const char *name) {
return i; return i;
} }
} }
// Fallback: 找不到精确匹配时 (例如 PoC 阶段 bin 里只有一张表情但代码映射到 m06/m07),
// 用 cache 里第一个可用 EAF 替代, 保证设备能显示动画 (验证阶段方便用)
// 注: 实际产品阶段所有情绪都该有对应 EAF, 这条 fallback 仅 PoC 兜底
if (s_eaf_cache_count > 0 && s_eaf_cache[0].data) {
ESP_LOGW(TAG, "Asset %s 不在 cache, fallback 到 %s", name, s_eaf_cache[0].name);
return 0;
}
return -1; return -1;
} }
@ -186,8 +197,11 @@ static esp_err_t switch_emotion_by_asset(const char *asset_name) {
}; };
gfx_anim_set_src_desc(s_anim_obj, &src); gfx_anim_set_src_desc(s_anim_obj, &src);
// 居中显示hiyori 209×360 居中放 360×360 屏 // 贴底显示, 让 240×320 数字人脚部紧贴屏底
gfx_obj_align(s_anim_obj, GFX_ALIGN_CENTER, 0, 0); // 底部 40px (= 360-320) 给数字人脚部 (跟之前 360×360 EAF 视觉一致)
// 顶部 40px 留透明空白 (chroma key 透明, 露出背景图)
// 字幕 56 高度在数字人上层 (z-index), 显示时覆盖最下方约 56px (含脚部)
gfx_obj_align(s_anim_obj, GFX_ALIGN_BOTTOM_MID, 0, 0);
// 全部帧 + EAF_DEFAULT_FPS + 永远循环 // 全部帧 + EAF_DEFAULT_FPS + 永远循环
gfx_anim_set_segment(s_anim_obj, 0, 0xFFFFFFFF, EAF_DEFAULT_FPS, true); gfx_anim_set_segment(s_anim_obj, 0, 0xFFFFFFFF, EAF_DEFAULT_FPS, true);
@ -260,14 +274,22 @@ void ai_chat_screen_init(void) {
} }
uint32_t file_count = header[12] | (header[13] << 8) | (header[14] << 16) | (header[15] << 24); uint32_t file_count = header[12] | (header[13] << 8) | (header[14] << 16) | (header[15] << 24);
ESP_LOGI(TAG, " MMAP file_count=%u", (unsigned)file_count); ESP_LOGI(TAG, " MMAP file_count=%u", (unsigned)file_count);
// 自动适配新旧两版 EAF Packer 的 MMAP 格式 (2026-05-20 新版 Packer entry 变 32B)
// 旧版: header[8] = 0x10, entry 28B = name(16) + size(4) + offset(4) + pad(4)
// 新版: header[8] = 0x14, entry 32B = name(16) + reserved(4) + size(4) + offset(4) + pad(4)
const bool is_new_mmap = (header[8] == 0x14);
const size_t ENTRY_SIZE = is_new_mmap ? 32 : 28;
const size_t FSIZE_OFFSET = is_new_mmap ? 20 : 16;
const size_t FOFFSET_OFFSET = is_new_mmap ? 24 : 20;
ESP_LOGI(TAG, " MMAP 版本=%s, ENTRY_SIZE=%zu",
is_new_mmap ? "新版(0x14)" : "旧版(0x10)", ENTRY_SIZE);
// 跳过 reserved 16B 到 entry table 起点 (0x20) // 跳过 reserved 16B 到 entry table 起点 (0x20)
fseek(f, 0x20, SEEK_SET); fseek(f, 0x20, SEEK_SET);
const size_t ENTRY_SIZE = 28; // 16 + 4 + 4 + 4
const size_t DATA_START = 0x20 + file_count * ENTRY_SIZE; const size_t DATA_START = 0x20 + file_count * ENTRY_SIZE;
s_eaf_cache_count = 0; s_eaf_cache_count = 0;
for (uint32_t i = 0; i < file_count; i++) { for (uint32_t i = 0; i < file_count; i++) {
uint8_t entry[28]; uint8_t entry[32]; // 按最大 entry 大小分配, 兼容旧/新版
fseek(f, 0x20 + i * ENTRY_SIZE, SEEK_SET); fseek(f, 0x20 + i * ENTRY_SIZE, SEEK_SET);
if (fread(entry, 1, ENTRY_SIZE, f) != ENTRY_SIZE) { if (fread(entry, 1, ENTRY_SIZE, f) != ENTRY_SIZE) {
ESP_LOGE(TAG, " entry[%u] 读取失败", (unsigned)i); ESP_LOGE(TAG, " entry[%u] 读取失败", (unsigned)i);
@ -276,8 +298,14 @@ void ai_chat_screen_init(void) {
char name[17] = {0}; char name[17] = {0};
memcpy(name, entry, 16); memcpy(name, entry, 16);
name[16] = '\0'; name[16] = '\0';
uint32_t fsize = entry[16] | (entry[17] << 8) | (entry[18] << 16) | (entry[19] << 24); uint32_t fsize = entry[FSIZE_OFFSET]
uint32_t foffset = entry[20] | (entry[21] << 8) | (entry[22] << 16) | (entry[23] << 24); | (entry[FSIZE_OFFSET + 1] << 8)
| (entry[FSIZE_OFFSET + 2] << 16)
| (entry[FSIZE_OFFSET + 3] << 24);
uint32_t foffset = entry[FOFFSET_OFFSET]
| (entry[FOFFSET_OFFSET + 1] << 8)
| (entry[FOFFSET_OFFSET + 2] << 16)
| (entry[FOFFSET_OFFSET + 3] << 24);
// 只缓存 .eaf 文件 // 只缓存 .eaf 文件
size_t nlen = strlen(name); size_t nlen = strlen(name);

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 442 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 398 KiB