☰
AIWatch — ESP32-S3 AI 语音可穿戴终端:把 endpoint 改到 TaoToken 的完整配置
2026/10/8 12:30:58 网站建设 项目流程

1. 为什么要在 AIWatch 上改 endpoint

AIWatch 是一套跑在 ESP32-S3 上的开源 AI 语音可穿戴终端固件,核心能力是把一块带 AMOLED 屏、双麦克风、扬声器和 SD 卡槽的开发板,变成一个能离线唤醒、自然语音对话、还能播放 MP3 的随身 AI 伴侣。它默认通过 WebSocket 连接自托管的 OpenClaw 网关,再由网关去调用 STT(语音转文字)和 TTS(文字转语音)服务。问题就出在这里:对大多数嵌入式开发者来说,自己搭一套 OpenClaw 网关、再单独申请小米 MiMo 的 ASR/TTS 密钥,前期成本太高,而且一旦网关挂了,整台设备就变成一块只会亮屏的砖。

我试过在本地用 Docker 起 OpenClaw,光是 ED25519 设备凭据的生成和网关侧的白名单配置就折腾了一下午,更别说还要维护 STT 和 TTS 两条独立的 HTTP 链路。对于只想验证「ESP32-S3 能不能跑通端到端语音对话」的人来说,这套架构太重了。真正想要的是一个统一的 Key/API 通道:设备端只认一个 Base URL、一个 Key,剩下的模型路由、鉴权、流式回包都由服务端处理。

TaoToken 在这里扮演的就是这个统一通道的角色。它对外暴露 OpenAI 兼容的 HTTP 接口,STT、TTS、LLM 都可以走同一套鉴权和 endpoint 规范。你要做的,是把 AIWatch 固件里原本指向 OpenClaw 网关和 MiMo 平台的地址,改成 TaoToken 的 API 地址,把分散的密钥收敛成一个 Key。改完之后,语音上行、模型回包、TTS 流式播放这三段链路依然完整,但配置复杂度从「网关 + 两个平台密钥」降到「一个 Base URL + 一个 Key」。

这篇文章面向已经拿到 Waveshare ESP32-S3-Touch-AMOLED-2.06 或类似板卡、能编译 ESP-IDF 工程的嵌入式开发者。我会从工程结构讲起,给出可以直接复制的 endpoint 和鉴权配置片段,然后带你用串口 CLI 验证语音上行和模型回包是否跑通,最后给一份失败排查清单。全程不涉及任何网络加速工具,所有请求都走正常的 HTTPS 出站。

需要提前说明的是,AIWatch 的语音链路分三段:唤醒词在本地由 ESP-SR 的 WakeNet 引擎处理,不联网;录音结束后音频通过 HTTP 上传到 STT 服务;STT 返回文本后,再由 LLM 生成回复,最后 TTS 把回复转成音频流回传播放。我们要改的是后三段的 endpoint 和鉴权字段,唤醒词部分保持原样。

2. TaoToken 前置准备与工程定位

在动固件代码之前,先把服务端这边的事情理清楚。TaoToken 的 API 入口是https://taotoken.net/api,它兼容 OpenAI 的接口规范,也就是说你原来调/v1/chat/completions、/v1/audio/transcriptions、/v1/audio/speech这些路径的代码,只需要把 Base URL 换掉、Key 换掉就能跑。对于 AIWatch 这种固件项目,这一点很关键,因为它的 STT 和 TTS 组件本来就是按 OpenAI 格式写的 HTTP 请求,改造成本极低。

你需要先拿到一个 API Key。登录 TaoToken 控制台,在 API Keys 页面创建一个新 Key,复制下来。这个 Key 会同时用于 STT、TTS 和 LLM 三条链路,不需要为每个服务单独申请。如果你还没注册,可以先访问官网了解服务范围,再进控制台创建 Key。整个流程不涉及任何特殊网络配置,正常浏览器访问即可。

接下来是工程定位。AIWatch 的仓库结构里,和网络请求相关的组件集中在components/目录下:

  • components/stt/:语音转文字,默认走 MiMo ASR 的 HTTP REST 接口
  • components/tts/:文字转语音,默认走 MiMo TTS 的 HTTP SSE 流式接口
  • components/openclaw/:WebSocket 客户端,负责和 OpenClaw 网关通信,带 ED25519 认证
  • main/include/secrets.h:存放 WiFi 凭据和各服务 API Key 的配置文件

我们的改造策略是:保留openclaw组件的 WebSocket 框架不动(因为状态机和资源仲裁都依赖它),但把它连接的 host 和 port 指向 TaoToken 的兼容端点;同时把stt和tts组件里的 Base URL 和鉴权头改成 TaoToken 的规范。这样改动量最小,也不会破坏原有的 15 态状态机。

在开始改代码前,先确认你的开发环境就绪:

# 确认 ESP-IDF 版本,需要 v5.5 及以上 idf.py --version # 确认目标芯片 idf.py set-target esp32s3 # 确认工程能正常编译(改代码前先跑一次基线) idf.py build

如果基线编译就报错,先解决工具链问题,不要急着改 endpoint。常见的基线错误是 ESP-IDF 版本低于 v5.5,导致esp_lcd_sh8601驱动不兼容。这种情况下先按官方文档升级 IDF。

另外,secrets.h是从secrets.h.example复制出来的,默认不在 git 跟踪范围内。你需要先执行:

cp main/include/secrets.h.example main/include/secrets.h

然后在这个文件里填入 WiFi SSID、密码,以及我们接下来要配的 TaoToken Key。这个文件是固件里所有敏感信息的唯一入口,改它比散落在各个组件里改宏定义要清晰得多。

3. 可复制的 endpoint 与鉴权配置

这一节是全文的核心,所有片段都可以直接复制到你的工程里。我会按「配置文件 → STT 组件 → TTS 组件 → OpenClaw 客户端」的顺序给出改动点,每个片段都标注了文件路径。

3.1 secrets.h 里的统一 Key 配置

打开main/include/secrets.h,把原来分散的 MiMo Key 和 OpenClaw 凭据替换成 TaoToken 的统一配置:

// main/include/secrets.h #pragma once // WiFi 配置 #define CONFIG_WIFI_SSID "你的WiFi名称" #define CONFIG_WIFI_PASSWORD "你的WiFi密码" // TaoToken 统一 API 配置 #define TAOTOKEN_API_BASE "https://taotoken.net/api" #define TAOTOKEN_API_KEY "sk-你的TaoToken密钥" // 模型 ID 配置(按需替换为你账号下可用的模型) #define TAOTOKEN_STT_MODEL "whisper-1" #define TAOTOKEN_TTS_MODEL "tts-1" #define TAOTOKEN_LLM_MODEL "gpt-4o-mini" // OpenClaw 兼容端点(指向 TaoToken 的 WebSocket 兼容入口) #define OPENCLAW_GATEWAY_HOST "taotoken.net" #define OPENCLAW_GATEWAY_PORT 443 #define OPENCLAW_GATEWAY_PATH "/api/ws"

这里有几个点要注意。第一,TAOTOKEN_API_BASE不带尾部斜杠,组件里拼接路径时统一用%s/v1/...的格式。第二,OPENCLAW_GATEWAY_HOST只填域名,不带https://前缀,因为 WebSocket 客户端会自己处理 TLS。第三,模型 ID 不是固定的,你要根据自己账号下实际可用的模型来填,STT 和 TTS 的模型名如果和示例不同,以控制台里显示的为准。

3.2 STT 组件的 endpoint 改造

找到components/stt/目录下的请求构造代码。通常是一个stt_request.c或类似文件,里面会有硬编码的 MiMo API 地址。把它改成从secrets.h读取:

// components/stt/stt_request.c(片段) #include "secrets.h" static const char *STT_ENDPOINT_FMT = "%s/v1/audio/transcriptions"; esp_err_t stt_build_request(char *url_buf, size_t url_len, char *auth_buf, size_t auth_len) { // 拼接完整 endpoint snprintf(url_buf, url_len, STT_ENDPOINT_FMT, TAOTOKEN_API_BASE); // 构造 Bearer 鉴权头 snprintf(auth_buf, auth_len, "Authorization: Bearer %s", TAOTOKEN_API_KEY); return ESP_OK; }

对应的 HTTP 请求头里,除了Authorization,还要确保Content-Type是multipart/form-data,因为音频上传走的是表单格式。如果你原来的代码用的是 MiMo 特有的鉴权字段(比如X-Api-Key之类),全部替换成标准的Authorization: Bearer。

3.3 TTS 组件的 endpoint 改造

TTS 走的是 SSE 流式输出,改动点在components/tts/下。核心是把请求 URL 和鉴权头换成 TaoToken 规范:

// components/tts/tts_stream.c(片段) #include "secrets.h" static const char *TTS_ENDPOINT_FMT = "%s/v1/audio/speech"; esp_err_t tts_build_stream_request(char *url_buf, size_t url_len, char *auth_buf, size_t auth_len, const char *text) { snprintf(url_buf, url_len, TTS_ENDPOINT_FMT, TAOTOKEN_API_BASE); snprintf(auth_buf, auth_len, "Authorization: Bearer %s", TAOTOKEN_API_KEY); // 请求体 JSON,注意 model 和 voice 字段 // {"model":"tts-1","input":"...","voice":"alloy","stream":true} return ESP_OK; }

TTS 的请求体是 JSON,stream字段设为true才能拿到 SSE 流。如果你不需要流式播放,可以设为false,但 AIWatch 的 TTS 播放器是按流式设计的,建议保持true。

3.4 OpenClaw 客户端的 host 与 port 改造

components/openclaw/里的 WebSocket 客户端默认连自托管网关。找到初始化连接的地方,把 host 和 port 改成从secrets.h读取:

// components/openclaw/openclaw_client.c(片段) #include "secrets.h" static esp_websocket_client_config_t ws_cfg = { .uri = "wss://" OPENCLAW_GATEWAY_HOST OPENCLAW_GATEWAY_PATH, .port = OPENCLAW_GATEWAY_PORT, .transport = WEBSOCKET_TRANSPORT_OVER_SSL, .reconnect_timeout_ms = 5000, .network_timeout_ms = 10000, };

注意 URI 用的是wss://而不是ws://,因为 TaoToken 的入口是 HTTPS,WebSocket 必须走 TLS。ED25519 设备凭据那部分如果 TaoToken 侧不需要,可以在配置里关掉,或者保留但填一个占位值。具体以你账号下的接入文档为准。

3.5 配置对照表

为了让你一眼看清改了哪些字段,我整理了一张对照表:

配置项改造前改造后
STT Base URLMiMo 平台地址https://taotoken.net/api
TTS Base URLMiMo 平台地址https://taotoken.net/api
鉴权字段平台专有字段Authorization: Bearer <Key>
OpenClaw Host自托管网关 IPtaotoken.net
OpenClaw Port自定义端口443
Key 数量多个平台密钥一个统一 Key

改完这些,执行idf.py build确认编译通过。如果报错说找不到secrets.h,检查一下main/include/是否在 include 路径里,以及 CMakeLists 有没有把main目录加进去。

4. 串口验证语音上行与模型回包

编译通过后,把固件烧录到板子上,用串口 CLI 验证整条链路。烧录命令:

idf.py -p /dev/ttyACM0 flash monitor

Windows 下端口可能是COM3之类,按设备管理器里显示的实际端口填。烧录完成后,串口会输出启动日志,你会看到状态机从BOOT走到CONNECTING,再走到IDLE。如果卡在CONNECTING,说明 WebSocket 没连上,先跳到第 5 节排查。

4.1 用 CLI 验证文本链路

在串口 CLI 里输入status,确认设备状态:

AIWatch> status State: IDLE WiFi: connected (192.168.1.100) Gateway: connected STT endpoint: https://taotoken.net/api/v1/audio/transcriptions TTS endpoint: https://taotoken.net/api/v1/audio/speech

如果Gateway显示disconnected,说明 WebSocket 没连上。如果 endpoint 显示的还是旧地址,说明secrets.h没生效,检查一下是不是改错了文件。

接下来用say命令直接发文本给 AI,跳过录音和 STT,先验证 LLM 和 TTS 链路:

AIWatch> say 你好,请用一句话介绍你自己

正常的话,你会看到状态机依次经过THINKING→STREAMING→RESPONSE→TTS_LOADING→TTS_PLAYING,然后扬声器里传出 AI 的语音回复。串口日志里会打印出模型返回的文本,类似:

[LLM] response: 你好,我是运行在 ESP32-S3 上的 AI 语音助手... [TTS] streaming 12800 bytes [TTS] playback done

这一步跑通,说明 LLM 和 TTS 的 endpoint 和鉴权都对了。

4.2 验证语音上行链路

文本链路通了之后,再验证录音和 STT。输入talk命令进入语音对话模式:

AIWatch> talk State: LISTENING 请说话...

对着板子的麦克风说一句话,比如「今天天气怎么样」。说完后,VAD 会检测到静音并结束录音,状态机进入SENDING,音频上传到 STT。串口会打印转写结果:

[STT] transcript: 今天天气怎么样 [LLM] response: 我无法获取实时天气,但可以帮你...

如果[STT] transcript是空的或者乱码,说明音频上传或转写有问题,检查 STT 的 endpoint 和Content-Type是否正确。

4.3 用 curl 做旁路验证

如果你怀疑是固件问题,可以先用 curl 在电脑上验证 TaoToken 的接口是否正常:

# 验证 LLM 接口 curl -s https://taotoken.net/api/v1/chat/completions \ -H "Authorization: Bearer sk-你的Key" \ -H "Content-Type: application/json" \ -d '{"model":"gpt-4o-mini","messages":[{"role":"user","content":"hi"}]}' # 验证 TTS 接口 curl -s https://taotoken.net/api/v1/audio/speech \ -H "Authorization: Bearer sk-你的Key" \ -H "Content-Type: application/json" \ -d '{"model":"tts-1","input":"test","voice":"alloy"}' \ --output test.mp3

如果 curl 能通而固件不通,问题就在固件侧;如果 curl 也不通,问题在 Key 或网络侧。这种二分法能帮你快速定位。

4.4 观察内存与状态

AIWatch 有内存监控组件,串口会周期性打印 DRAM/PSRAM 使用情况。语音链路跑通后,观察一下内存是否稳定:

[MEM] DRAM free: 182KB, PSRAM free: 6.2MB

如果 DRAM 持续下降,可能是 HTTP 响应没释放,检查一下esp_http_client的 cleanup 逻辑。PSRAM 主要给 LVGL 和音频缓冲用,正常应该在 6MB 以上。

5. 常见报错排查清单

这一节按真实报错来组织,每条都给出原因和修复方法。

5.1 401 Unauthorized

串口日志里出现:

[STT] HTTP status: 401 [STT] response: {"error":{"message":"Invalid API key"}}

原因通常是 Key 填错、Key 过期,或者鉴权头格式不对。检查secrets.h里的TAOTOKEN_API_KEY是否完整复制,有没有多余空格。鉴权头必须是Authorization: Bearer sk-xxx,注意Bearer和 Key 之间有一个空格。如果你用的是环境变量注入,确认编译时变量确实传进去了。

5.2 local proxy failed / connection refused

[OpenClaw] websocket connect failed: local proxy failed

这个报错说明 WebSocket 客户端尝试走本地代理但失败了。检查ws_cfg里有没有误设.proxy字段,把它删掉或设为 NULL。另外确认transport是WEBSOCKET_TRANSPORT_OVER_SSL,端口是 443。如果你的开发环境有全局代理,先关掉再试。

5.3 reading choices 解析失败

[LLM] parse error: reading choices: unexpected end of JSON

这是 LLM 返回的 JSON 不完整导致的。常见原因是 HTTP 响应体太大,接收缓冲区不够。检查esp_http_client的buffer_size配置,建议设为 4096 以上。另外确认你请求的模型 ID 是有效的,如果模型名写错,服务端可能返回一个非标准 JSON,导致解析失败。

5.4 OAuth / token 过期

[Gateway] auth failed: OAuth token expired

如果你保留了 OpenClaw 的 ED25519 认证逻辑,而 TaoToken 侧不需要这个认证,就会出现这个报错。解决方法是在openclaw_client.c里把认证回调设为 NULL,或者填一个占位凭据。具体以接入文档为准。

5.5 状态机卡在 CONNECTING

如果串口一直显示State: CONNECTING,先确认 WiFi 是否连上(status命令会显示)。WiFi 正常但 WebSocket 连不上,检查OPENCLAW_GATEWAY_PATH是否正确,以及 TaoToken 侧是否支持 WebSocket 接入。如果不支持,可以改用 HTTP 轮询模式,或者只保留 STT/TTS 的 HTTP 链路,LLM 部分用say命令触发。

5.6 排查速查表

报错关键词最可能原因修复动作
401 UnauthorizedKey 错误或格式不对检查 Bearer 头
local proxy failed误设代理字段删除 proxy 配置
reading choices缓冲区太小或模型名错增大 buffer,核对模型 ID
OAuth expired多余认证逻辑关闭 ED25519 回调
卡在 CONNECTINGWebSocket 路径错核对 path 和端口

排查时建议打开详细日志,在 menuconfig 里把 log level 设为 Debug,这样能看到完整的 HTTP 请求和响应头,定位问题会快很多。

6. 把统一通道用起来

改完 endpoint 之后,AIWatch 的语音链路就收敛到一条通道上了。你不再需要维护 OpenClaw 网关和 MiMo 平台两套密钥,STT、TTS、LLM 共用同一个 Key,换模型只需要改secrets.h里的模型 ID。对于嵌入式开发者来说,这意味着你可以把精力放在硬件调试和交互优化上,而不是服务端的运维。

如果你后续要做长期编码或 Agent 类的实验,可以了解一下 Coding Plan,它适合需要持续调用模型的场景。如果只是想验证某个模型的效果,可以直接在模型对话页面里试。接入过程中遇到鉴权或 endpoint 问题,API Keys 页面和接入文档是最直接的参考。

最后给一个实用建议:把secrets.h加入.gitignore,避免 Key 泄露。如果你要分享工程给别人,用secrets.h.example做模板,把 Key 留空。另外,TTS 的流式播放对网络抖动比较敏感,如果发现播放卡顿,可以在tts_stream.c里把接收缓冲区调大,或者把stream设为false改用整段下载再播放。这些细节调优,比改 endpoint 更能影响最终体验。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询