 文件读写:把 SPIFFS 挂载到 TaoToken 的配置记录)
1. 从一次串口报错说起MiniClaw 文件读写到底难在哪如果你正在 ESP32-S3 上折腾 OpenClaw 具身硬件大概率会遇到这个场景Telegram 消息进来了LLM 决定调用read_file结果串口打印E (12345) SPIFFS: mount failed, -10025或者tool_result: file not found。这不是模型的问题而是 SPIFFS 挂载点和路径映射没对齐。MiniClaw 在 ESP32-S3 上的文件读写链路核心就三件事把 SPIFFS 分区挂到 VFS 挂载点、把逻辑路径映射到物理路径、用标准 POSIX API 做读写。听起来简单但 ESP-IDF 的 SPIFFS 组件有几个坑分区表里spiffs子类型必须写对、base_path和partition_label要匹配、挂载失败时esp_spiffs_format的调用时机不对会丢数据。我实测下来MiniClaw 的tool_read_file_execute最终落到fopen(/spiffs/skills/weather.md, r)中间经过 VFS 层把/spiffs前缀剥掉再交给 SPIFFS 驱动去查分区表。所以只要挂载点、分区标签、路径前缀三者一致读写就能通。这篇笔记就按这个顺序把可复制的分区表、挂载代码、读写验证动作全部拆开。适合谁看已经在跑 ESP-IDF、手里有 ESP32-S3 开发板、想复现 MiniClaw 文件系统行为的嵌入式开发者。不需要你懂向量数据库但需要你会用idf.py menuconfig和看串口日志。2. TaoToken 前置为什么文件读写链路要先接上模型服务MiniClaw 的read_file不是孤立工具它被 LLM 的tool_use触发。也就是说你得先有一个能返回tool_use的模型服务才能完整验证“LLM 决定读文件 → agent_loop 执行 → tool_result 回填”这条链路。TaoToken 在这里的角色是提供兼容 Anthropic 和 OpenAI 两种协议的 API 入口MiniClaw 的llm_client.c里通过MIMI_LLM_PROVIDER宏切换。你需要准备三样东西Base URL、API Key、Model ID。Base URL 用https://taotoken.net/api不要加 UTM 参数这是给代码里http_client用的。API Key 在控制台生成Model ID 根据你选的提供商填比如claude-sonnet-4-20250514或gpt-4o。这里有个容易踩的坑MiniClaw 的context_builder.c会把工具列表塞进 system prompt如果模型服务返回的tool_use格式和解析代码不匹配你会看到tool_use字段为空然后 agent_loop 直接跳过文件读取。所以接上模型服务后先用curl验证一次tool_use返回结构再烧录固件。具体操作打开 TaoToken 控制台生成一个 API Key复制下来。然后本地用 curl 测一下curl -X POST https://taotoken.net/api/v1/messages \ -H Content-Type: application/json \ -H x-api-key: sk-你的Key \ -H anthropic-version: 2023-06-01 \ -d { model: claude-sonnet-4-20250514, max_tokens: 1024, tools: [{ name: read_file, description: Read a file from SPIFFS storage., input_schema: { type: object, properties: { path: {type: string} }, required: [path] } }], messages: [{role: user, content: 读取 /spiffs/skills/weather.md}] }如果返回 JSON 里有type: tool_use和name: read_file说明模型服务侧通了。这一步不做后面 SPIFFS 挂载再成功你也看不到完整的文件读写链路。3. 可复制配置SPIFFS 分区表与挂载代码逐行拆解3.1 分区表 partitions.csvMiniClaw 默认用 4MB Flash分区表里 SPIFFS 分区通常放在factory之后。关键字段是Type和SubTypeSPIFFS 必须写data和spiffs# Name, Type, SubType, Offset, Size, Flags nvs, data, nvs, 0x9000, 0x6000, phy_init, data, phy, 0xf000, 0x1000, factory, app, factory, 0x10000, 0x300000, spiffs, data, spiffs, 0x310000,0xF0000,0x310000是 SPIFFS 起始地址0xF0000是 960KB 空间。如果你板子是 8MB Flash可以把factory和spiffs都放大但注意Offset要连续不能重叠。3.2 menuconfig 里的关键项运行idf.py menuconfig进Component config → SPIFFS ConfigurationSPIFFS_MAX_PARTITIONS设为 3够用SPIFFS_USE_MAGIC打开SPIFFS_USE_MAGIC_LENGTH也打开SPIFFS_OBJ_NAME_LEN设为 64MiniClaw 的路径/spiffs/skills/weather.md有 28 字符64 够SPIFFS_USE_MMAP关掉ESP32-S3 上 mmap 对 SPIFFS 支持不完整3.3 挂载代码 spiffs_init.c这是 MiniClaw 里storage_init()的简化版你可以直接复制#include esp_spiffs.h #include esp_log.h static const char *TAG SPIFFS_INIT; esp_err_t spiffs_mount(void) { esp_vfs_spiffs_conf_t conf { .base_path /spiffs, .partition_label spiffs, .max_files 8, .format_if_mount_failed true }; esp_err_t ret esp_vfs_spiffs_register(conf); if (ret ! ESP_OK) { if (ret ESP_FAIL) { ESP_LOGE(TAG, Mount or format failed); } else if (ret ESP_ERR_NOT_FOUND) { ESP_LOGE(TAG, Partition spiffs not found); } else { ESP_LOGE(TAG, SPIFFS init failed: %s, esp_err_to_name(ret)); } return ret; } size_t total 0, used 0; ret esp_spiffs_info(spiffs, total, used); if (ret ESP_OK) { ESP_LOGI(TAG, Partition size: total%d, used%d, total, used); } return ESP_OK; }注意base_path是/spiffspartition_label是spiffs这两个必须和分区表里的Name一致。format_if_mount_failed true在开发阶段方便但量产固件建议改成false避免意外格式化丢数据。3.4 路径映射规则MiniClaw 的MIMI_SPIFFS_BASE宏定义为/spiffs。当 LLM 传path/spiffs/skills/weather.md时tool_read_file_execute直接把这个字符串传给fopen。VFS 层看到/spiffs前缀剥掉后交给 SPIFFS 驱动驱动在分区里找skills/weather.md。所以你在 SPIFFS 里创建文件时路径是/spiffs/skills/weather.md但实际存储的 key 是skills/weather.md。4. 验证请求一次写入、读取、校验的完整动作4.1 写入文件在app_main里挂载成功后先写一个测试文件#include stdio.h #include string.h void test_spiffs_write_read(void) { const char *path /spiffs/skills/weather.md; const char *content # Weather Skill\n\n当用户问天气时调用 get_weather 工具。\n; FILE *f fopen(path, w); if (f NULL) { ESP_LOGE(TAG, Failed to open file for writing); return; } fwrite(content, 1, strlen(content), f); fclose(f); ESP_LOGI(TAG, File written: %s, path); // 读取校验 f fopen(path, r); if (f NULL) { ESP_LOGE(TAG, Failed to open file for reading); return; } char buf[256] {0}; size_t read_bytes fread(buf, 1, sizeof(buf) - 1, f); fclose(f); ESP_LOGI(TAG, Read %d bytes: %s, read_bytes, buf); if (strcmp(buf, content) 0) { ESP_LOGI(TAG, Verify OK); } else { ESP_LOGE(TAG, Verify FAILED); } }烧录后串口应该打印I (1234) SPIFFS_INIT: Partition size: total983040, used0 I (1235) SPIFFS_INIT: File written: /spiffs/skills/weather.md I (1236) SPIFFS_INIT: Read 52 bytes: # Weather Skill... I (1237) SPIFFS_INIT: Verify OK4.2 通过 LLM 触发 read_file文件写好后把read_file工具注册进 agent_loop然后发一条 Telegram 消息或串口模拟消息“读一下 weather.md”。串口日志应该出现[agent] tool_use: read_file(path/spiffs/skills/weather.md) [agent] tool_result: 52 bytes [agent] final answer: 文件内容是...如果tool_result是 0 bytes检查fopen返回值如果是file not found检查路径前缀和分区挂载点。4.3 校验动作除了strcmp还可以用esp_spiffs_info看used字节数变化。写入前used0写入后used52实际会按块对齐可能显示 4096。如果used没变说明写入没落盘检查fclose是否调用。5. 本篇常见错排查401、mount failed、reading choices、OAuth5.1 401 Unauthorized串口打印HTTP 401通常是 API Key 没填对或 header 名字写错。MiniClaw 的llm_client.c里 Anthropic 用x-api-keyOpenAI 用Authorization: Bearer。检查MIMI_LLM_API_KEY宏是否被正确赋值以及 TaoToken 控制台里 Key 是否被禁用。5.2 mount failed, -10025-10025是ESP_ERR_NOT_FOUND意思是分区表里没找到spiffs分区。检查partitions.csv是否被idf.py menuconfig → Partition Table → Custom partition table CSV正确引用以及SubType是否写成spiffs而不是fat。5.3 reading choices 报错如果串口打印failed to parse choices或reading choices说明模型返回的 JSON 里tool_use字段结构和你代码里的cJSON_GetObjectItem路径不匹配。Anthropic 的tool_use在content数组里OpenAI 的在choices[0].message.tool_calls。MiniClaw 用MIMI_LLM_PROVIDER区分检查宏是否和实际 API 一致。5.4 OAuth 相关错误如果你看到OAuth token expired或invalid_grant说明用了 OAuth 流程而不是 API Key。MiniClaw 的llm_client.c只支持 API Key 模式OAuth 需要额外实现 token 刷新。建议直接用 TaoToken 控制台生成的 API Key不要走 OAuth。5.5 CC Switch / Cline MCP / Codex auth.json 三件套如果你在 PC 侧用 CC Switch 或 Cline MCP 调试 MiniClaw 的 API 配置需要写全三件套{ base_url: https://taotoken.net/api, api_key: sk-你的Key, model_id: claude-sonnet-4-20250514 }Codex 的auth.json里对应字段是api_base、api_key、model。少任何一个工具调用都会失败。6. 语义一致 CTA把文件读写链路接进你的 MiniClaw文件读写链路跑通后下一步是把read_file、write_file、list_dir三个工具都注册进 agent_loop然后让 LLM 自己决定什么时候读、什么时候写。MiniClaw 的context_builder.c会把MEMORY.md和Skills目录页塞进 system promptLLM 看到目录后发起read_filetool_result回填后再生成回答。如果你还没接上模型服务先去 TaoToken 控制台生成 API Key然后参考接入文档把llm_client.c里的 Base URL 和 Key 填好。验证模型是否返回tool_use可以用模型对话页面直接发一条带工具定义的请求看返回 JSON 结构。长期跑编码或 Agent 场景Coding Plan 的额度更划算适合把 MiniClaw 当常驻设备用。我踩过的坑是SPIFFS 挂载成功后忘了调esp_vfs_spiffs_register的返回值检查结果fopen一直返回 NULL查了半天才发现是max_files设成了 0。把max_files改成 8 之后读写一次通过。