AI Passport

固件层 (C / ESP-IDF)

跑在 ESP32-C3 上的程序。它要做的事:录音、播放、联网、把声音切成帧搬出去、把云端语音搬回来播。

目标芯片:ESP32-C3 · 8MB Flash · 无 PSRAM · ESP-IDF 5.5.3 · FreeRTOS

说人话版:这是一块巴掌大的芯片,内存非常紧张(没有外挂内存)。所以固件里最讲究的是「省着用内存」——声音不落地、用环形缓冲、边录边发。下面每个文件就是一个「职责模块」,点开看里面每个关键函数在源码的第几行。

#模块清单(点开看函数级明细)

▶ wb_app.c 应用主逻辑:状态机、录音/播放编排、按键处理 52.5 KB

这是「大脑的调度中心」。它不亲自碰音频字节,而是调用 wb_audio 去录/放、调用 wb_link 去收发,自己管「现在该干嘛」的状态机。改动最密集的文件。

1146esp_err_t wb_app_start(void)

其余内部函数(send_recording 流水线发送、handle_audio_ack、finish_voice_capture、on_play_frame 停等流控等)为 static 函数,散布在文件内,由上述入口统一驱动。

▶ wb_anchor.c 公网锚点链路:WebSocket 客户端、心跳、重连 35.6 KB

负责「卡片怎么连上 Cloudflare 锚点」。管 WebSocket 的建立、心跳保活、断线重连。也是本轮 P11「重连被拒」bug 的修复点。

761esp_err_t wb_anchor_cfg_defaults(wb_anchor_cfg_t *cfg)
774bool wb_anchor_token_configured(void)
783esp_err_t wb_anchor_start(const wb_anchor_cfg_t *cfg, wb_ble_cb_t cb, void *user)
840esp_err_t wb_anchor_stop(void)
855bool wb_anchor_connected(void)
860bool wb_anchor_ready(void)
865esp_err_t wb_anchor_send_control(const char *line, size_t len)
876esp_err_t wb_anchor_send_audio(const uint8_t frame[WB_AUDIO_FRAME_SIZE])
884void wb_anchor_disconnect(void)
893wb_anchor_state_t wb_anchor_state(void)
918int wb_anchor_reconnects(void)
923bool wb_anchor_gw_online(void)
928int wb_anchor_pending(void)
▶ wb_audio.c 音频引擎:录音环缓冲、播放队列、CRC、丢帧策略 26.3 KB

项目里最核心、最讲究的模块。录音和播放各有一套环形缓冲;录音环写满时「丢最旧帧保最新」;CRC 从发送侧逐帧累计(丢掉的帧天然不计入)。

216uint32_t wb_audio_crc_update(uint32_t state, const uint8_t *buf, size_t len)
451size_t wb_audio_probe_rec_capacity(void)
472esp_err_t wb_audio_init(void)
513bool wb_audio_play_begin(uint32_t total)
531size_t wb_audio_play_push(const uint8_t *ulaw, size_t n)
539void wb_audio_play_end(bool drain)
554size_t wb_audio_play_free(void)
559bool wb_audio_play_active(void)
564float wb_audio_play_progress(void)
573bool wb_audio_rec_begin(void)
596size_t wb_audio_rec_read(uint8_t *out, size_t max)
604size_t wb_audio_rec_used(void)
609void wb_audio_rec_end(void)
614bool wb_audio_rec_active(void)
622size_t wb_audio_rec_capacity(void)
627size_t wb_audio_rec_target(void)
632float wb_audio_rec_speech_ratio(void)
640uint32_t wb_audio_rec_bytes(void)
647uint32_t wb_audio_rec_dropped(void)
652uint32_t wb_audio_rec_max_bytes(void)
664uint32_t wb_audio_rec_crc(void)
669void wb_audio_set_volume(uint8_t percent)
▶ wb_ble.c 蓝牙低功耗链路(近距离直连) 16.1 KB

卡片和电脑在同一个房间时,可以不走公网、直接用蓝牙连。提供和 wb_anchor 一样的收发接口。

343esp_err_t wb_ble_start(const char *name, wb_ble_cb_t cb, void *user)
405esp_err_t wb_ble_stop(void)
422bool wb_ble_connected(void)
427bool wb_ble_ready(void)
432int wb_ble_mtu(void)
470esp_err_t wb_ble_send_control(const char *line, size_t len)
478esp_err_t wb_ble_send_audio(const uint8_t frame[WB_AUDIO_FRAME_SIZE])
483void wb_ble_disconnect(void)
▶ wb_link.c 链路抽象层:统一 BLE / 锚点两条路 10.6 KB

让上层不关心「到底走的蓝牙还是锚点」。上层只调用 wb_link_send_audio,具体走哪条路由 wb_link_kind 决定。

99esp_err_t wb_link_start(const char *name, wb_ble_cb_t cb, void *user)
183esp_err_t wb_link_stop(void)
200bool wb_link_connected(void)
212bool wb_link_ready(void)
224esp_err_t wb_link_send_control(const char *line, size_t len)
236esp_err_t wb_link_send_audio(const uint8_t frame[WB_AUDIO_FRAME_SIZE])
248void wb_link_disconnect(void)
262wb_link_kind_t wb_link_kind(void)
279esp_err_t wb_link_use_ble(void)
300esp_err_t wb_link_use_anchor(void)
▶ wb_proto.c 协议编解码:JSON 控制消息 + 音频帧打包 16.6 KB

把「内存里的数据结构」和「线上字节流」互转。控制消息走 cJSON,音频帧是固定 243 字节的二进制结构。

42double wb_js_num(const cJSON *obj, const char *key, double fallback)
76void wb_line_init(wb_line_t *line)
90void wb_line_reset(wb_line_t *line)
101bool wb_line_push(wb_line_t *line, uint8_t byte)
136void wb_aframe_pack(uint16_t seq, const uint8_t *payload, uint8_t n, ...)
153void wb_aframe_unpack(const uint8_t in[WB_AUDIO_FRAME_SIZE], uint16_t *seq, ...)
277bool wb_ctrl_parse(const char *line, wb_ctrl_msg_t *msg)
▶ wb_ui.c 屏幕界面:页面切换、toast、刷新 24.9 KB

卡片小屏幕上的界面。注意:默认字体没有中文字形,中文显示需要专门字体方案。

580void wb_ui_set_page(wb_page_t page)
611wb_page_t wb_ui_page(void)
616void wb_ui_refresh(void)
651void wb_ui_toast(const char *text)
668void wb_ui_init(wb_ui_model_t *model)
709void wb_ui_mem_report(void)
▶ wb_ulaw.c μ-law 编解码(16 位 PCM ↔ 8 位 μ-law) 1.9 KB

极小的一个模块,做语音压缩编解码。把 16 位采样压成 8 位,带宽砍一半。

13uint8_t wb_ulaw_encode(int16_t pcm)
35int16_t wb_ulaw_decode(uint8_t ulaw)
49void wb_ulaw_encode_buf(const int16_t *pcm, uint8_t *out, size_t n)
56void wb_ulaw_decode_buf(const uint8_t *in, int16_t *pcm, size_t n)
▶ wb_wifi.c Wi-Fi 连接:扫描、凭据管理(NVS 持久化)、重连 24.1 KB

管 Wi-Fi。支持一个「凭据列表」(MRU,最近连上的排前面),存进 NVS 掉电不丢。这是设备「离线找不到网」问题的关键排查点。

197int wb_wifi_net_count(void)
210esp_err_t wb_wifi_net_add(const char *ssid, const char *pass)
245esp_err_t wb_wifi_net_del(int index)
259esp_err_t wb_wifi_net_clear(void)
438esp_err_t wb_wifi_init(void)
527esp_err_t wb_wifi_seed_from_defaults(void)
560esp_err_t wb_wifi_start(void)
574esp_err_t wb_wifi_stop(void)
596esp_err_t wb_wifi_scan_log(int max_n)
655wb_wifi_state_t wb_wifi_state(void)
675int wb_wifi_last_reason(void)
680void wb_wifi_set_cb(wb_wifi_event_cb_t cb)

#入口与凭据文件

文件作用注意
main.c程序入口,启动各模块—
wb_wifi_seed.local.hWi-Fi 凭据种子列表(编译期写入)⚠️ 含明文密码,不要提交进版本库
wb_anchor_secrets.local.h锚点令牌 / URL / 设备号⚠️ 含令牌,同样保密
demo_*.c(10 个)出厂演示程序(音频、电池、蓝牙、按钮、低功耗等)派生应用需重做 UI,不能复用演示壳
安全红线: wb_wifi_seed.local.h 和 wb_anchor_secrets.local.h 这两个文件里有明文 Wi-Fi 密码和令牌。项目规范明确「不得提交凭据、设备二维码密钥、私钥、个人数据」。对应仓库里有 .example 模板,真实值只在本地。