先确定这篇要完成什么
下载包内 firmware/ 来自小智 v2.4.0 的完整1590个跟踪文件,并显式应用两处 本站板局部兼容补丁(IDF5.5.5 I2C顺序 + MAX98357A输出16k)。上游提交为 5540258abcbfa62518d09959308200be1c5b1b2b,原许可和目录保留,不是只放下载脚本。UPSTREAM.json记录1590原文件SHA,COMPATIBILITY.json、AUDIO-COMPATIBILITY.json和两份patch分别记录两处兼容修改,可逆向恢复并逐SHA核对原树。
本篇改变的是SSD1306初始化时的一条串口日志,不会改变屏幕文字、提示音或按键。这样先证明“我的源码→我的配置→我的镜像”这条链,再做功能。本站没有执行这块DIY组合的实板刷写、联网激活或声音测试,各项物理结果保持待测。
参考:[1] S3 DevKitC1 v1.1固定文档:J1/J3、电源与USB[2] ICS43434 DS-000069 v1.2:电气/边沿/24bit/启动[4] MAX98357A/MAX98357B Rev7:供电/采样率/SD/GAIN/BTL
1. 先把版本路线分开
本包固定 ESP-IDF5.5.5。v2.4.0的manifest声明IDF >=5.5.2,但最低约束不保证每个板型原样可编译。该版部分说明/AGENTS已优先推荐6.x;实际原样构建在本板型遇到I2C结构体字段顺序硬错。本文因此明确为 v2.4.0 + 本站I2C顺序/功放16k两处明确兼容补丁,不称原样上游构建通过。
第一份兼容补丁只把compact_wifi_board.cc中的命名初始化顺序调整为5.5.5真实头文件顺序:回调/上下文前移,scl_speed_hz后移。字段值、I2C API、引脚和板型不变,不改SDK。它与下一步一条日志个性化修改分开保存和验证。
站内浏览器刷机/硬件准备页另外核对了 v2.5.2 发布包;v2.5.x的manifest需要IDF >=6.0.1,不能把它原样放进本教程5.5.5环境。这里不修改上游SDK版本约束,也不下载第二SDK冒充同一构建。切换tag时必须重查manifest、板型配置、分区和依赖锁。
本配置选择 bread-compact-wifi、SSD1306 128×32、简体中文、禁用唤醒词/音频处理器/AEC,用实体按键测试;这不是上游所有默认功能。没有编入WakeNet并不等于端上已经能离线理解任意语音;普通对话还需要所选后端、网络和账号关联。
参考:[8] 小智v2.4.0固定manifest:IDF最低约束[13] IDF5.5.5 LCD I2C真实字段顺序[18] 固定CMake:语言头生成目标与组件选择[19] 小智v2.5.2 manifest:>=6.0.1差异
2. 这是一套新的N16R8组合
仅覆盖 ESP32-S3-DevKitC-1 v1.1 / WROOM-1-N16R8:16MB Quad Flash、8MB Octal PSRAM、Flash80MHz、Octal PSRAM80MHz。按模组屏蔽罩后缀与板修订核对,不凭网店“16M”标题判断。A01/A02使用的是N8R8,不能把其构建或历史检查当成本组合已实测。也不把模块规格表当成当前商家库存/板卡SKU证明。
GPIO47是本源码使用的数字按钮,不能只因变量叫touch就换成电容触摸片。128×64 PID326不是本包显示配置,旧PID931需另核RESET,不作直接替代。PID4440固定制造商revB电路有自动复位,原始仓库README仍提旧PID931,本文按具体电路/产品页分别核对。显示地址0x3C、I2C400kHz,源码镜像X/Y均为true。
v1.1板载RGB在GPIO38,而本版bread配置的 BUILTIN_LED_GPIO=48 用于外接可寻址RGB。本实验不接GPIO48,不把它当板载灯。 GPIO18原版LampController会初始化为低,本篇先不接该LED;下一篇才替换该工具。GPIO35/36/37涉及Octal PSRAM,不用;USB原生19/20不接模块,下载/日志使用桥接UART0的43/44。
先断USB与功放外供,再接线、查极性/短路/共地。MAX98357A的BTL两个输出均不接地,不接耳机地或接地示波器夹。GPIO不带扬声器,功放正电源不与主板正端并联。首次物理SD开关保持断开,任何复位前也先关闭。
| 模块 | 固定选择与接线 |
|---|---|
| 麦克风 | 已可靠焊接排针的Adafruit PID6049 / ICS-43434;3V→主板3V3,GND/SEL→GND,WS4、BCLK5、DOUT6 |
| 数字功放 | 已可靠焊接排针的PID3006 / MAX98357A单声道;DIN7、BCLK15、LRC16;GAIN→其VIN选6dB |
| 功放供电 | 独立稳压3.3V、额定至少1A;VIN只接外供正端,外供负端与主板GND共地,不并接主板3V3/5V正端 |
| 扬声器 | PID1313 / C2464-001,8Ω/1W;两线接OUT+/OUT−,任何一端都不接GND |
| 物理静音 | SD接10kΩ到GND;另用低压SPST开关把SD经1kΩ接到功放VIN。开关断开为关闭、闭合为左声道启用;先测电压 |
| 显示 | Adafruit PID4440、0.91英寸128×32 SSD1306 I2C、自动复位版;VIN→主板3V3,GND→GND,SDA41、SCL42,RST留空 |
| 控制 | 板载BOOT GPIO0;另GPIO47常开按钮到GND、GPIO47外10kΩ上拉到主板3V3;可另用39/40各加同样按钮作音量减/增 |
参考:[1] S3 DevKitC1 v1.1固定文档:J1/J3、电源与USB[2] ICS43434 DS-000069 v1.2:电气/边沿/24bit/启动[3] PID6049固定PCB:3V、SEL与47k数据下拉[4] MAX98357A/MAX98357B Rev7:供电/采样率/SD/GAIN/BTL[5] PID3006固定单声道PCB:VIN/SD1M/BTL端子[6] PID1313扬声器2017-02-14厂家规格:8Ω额定1W[7] PID6049产品页:具体型号与停产边界[14] ESP32-S3-WROOM-1/1U v1.8:N16R8规格表[15] PID4440固定revB PCB:上拉/电平转换/自动复位/128×32[16] Adafruit PID4440:具体尺寸、自动复位版本[17] 固定board config:真实I2S/按钮/GPIO48/18
3. 读懂实际音频,而不是沿用别篇的处理结论
本版 NoAudioCodecSimplex 用两套独立I2S:输入16kHz左槽、32bit;输出16kHz左槽、32bit。ICS-43434的有效24bit数据位于32bit槽中,16kHz属于其低功耗范围。实际输入源码把带符号32bit值右移12位再限到16bit,含额外数值放大/削顶可能,不是A01的WAV转换策略;本包不声称已经校准麦克风增益或直流偏移。
输出把16bit PCM按软件音量平方比例放到32bit槽,不能把音量数字直接当dB、声压或已测输出功率。原版初始音量70,NVS还会保存旧音量;AudioCodec启动会把非正音量恢复为10。因此软件静音/NVS不是复位期间物理静音保证。保持SD开关关闭,先在日志/屏幕核音量、用音量减按钮降低,再小心启用外供和SD;出现啸叫/削波/热/压降立即关SD与外供。本例没有AEC。
本组合为什么必须把原版输出24k改为16k
MAX98357A Rev7 p6 DIGITAL AUDIO INTERFACE保证的LRCLK区间为7.6–8.4、15.2–16.8、30.4–50.4、83.8–100.8kHz。24k不在保证范围;原上游AUDIO_OUTPUT_SAMPLE_RATE=24000不能用来证明本站所选PID3006合规。第二份显式板局部补丁只把config.h此宏改16000,输入16k和所有引脚不变。两套I2S仍独立,不因频率相同就短接时钟。32bit×2slot标称BCLK=1.024MHz,物理时钟未测。
固定AudioService::SetDecodeSampleRate按到来的Opus packet sample_rate打开decoder。若服务端送24k,则以锁定esp_audio_effects1.3.0~1配置src=24000/dst=16000、mono16bit、complexity2;decode后通过get_max_out_sample_num分配容量、process实际输出数量缩放PCM,再入播放队列。组件头声明输入/输出rate为4000或11025倍数,24k和16k均符合。TX codec本身仍配置16k,不直接将服务端24k设成MAX98357A LRCLK。实际组件已链接;主机桩只查完整方法参数/分支,不执行Xtensa DSP,不证明音质或时钟。可在本机执行 python3 tools/check_rate_path.py --output /tmp/a07-rate-contract(输出目录须尚不存在;A08可换目录名),它从下载包生产源提取完整方法和转换条件块,验证24→16配置、容量/实际输出数、重复配置复用、同频旁路和空handle局限;编译器静态断言同时核所选LRCLK落在厂家保证区间。
上游重采样open失败只打印日志,没有自动静音保证;get_max_out_sample_num/process返回值也没有在本版全面处理。看到“Failed to create output resampler”或“Failed to create Opus decoder”、异常语速/音调或内存错误时先关闭物理SD启用(开关断开VIN、由10k下拉静音),再停止功放外供,检查供电/PSRAM/堆、decoder采样率与完整版本,恢复自己的归档或RESET,不继续把错误当正常转换。此处保留并准确说明原实现,未改SDK/managed算法或暗称故障已被自动安全处理。
原24k作者包和全部原产品留在历史,编译通过未曾证明器件频率适配;当前结果只认16k修正版的三阶段/工具版新构建。
参考:[2] ICS43434 DS-000069 v1.2:电气/边沿/24bit/启动[4] MAX98357A/MAX98357B Rev7:供电/采样率/SD/GAIN/BTL[10] 固定Simplex codec:使用板配置的采样率与数值转换[11] 固定AudioCodec:默认音量与NVS恢复[20] 固定AudioService:decoder采样率与输出重采样路径[21] 实际esp_audio_effects1.3.0~1来源:rate converter格式/采样率/API约束
4. 激活已有IDF环境,核对原树
先完成站内E02/E07对应的环境/依赖管理。激活你已安装的5.5.5环境,确认 idf.py --version。下面工具使用当前IDF_PATH和当前Python,不替你装/替换SDK。
依赖锁固定当前解析结果;不要先删除锁再称“复现同版本”。首次构建会读取完整源和相应组件,SDK、缓存、managed组件与build都需要空间;先保证可写余量,不要在磁盘满时反复启动多个构建。
本次原样首次并行构建真实遇到 assets/lang_config.h 缺失。固定上游CMake把 lang_header设为ALL目标,却未显式接到main编译依赖,显示源码可能先编译。工具先运行上游 idf.py lang_header,再build,不手工改生成头。生成头后,原样源码又因I2C字段顺序失败,另有一次空间门限主动停止;日志和原输入均保留,不能写成原样成功。下载包已经带明确兼容补丁,当前结果以补丁版构建证据为准。
python tools/source_state.py verify-baseline
python tools/project.py configure
python tools/project.py build
python tools/project.py manifest参考:[8] 小智v2.4.0固定manifest:IDF最低约束[13] IDF5.5.5 LCD I2C真实字段顺序[18] 固定CMake:语言头生成目标与组件选择
5. 保存基线,再只改一条日志
第一次成功后,将 local-products.json 和对应日志另存为baseline,复制其中列出的完整镜像/配置/锁到自己的只读归档,并逐SHA核对。镜像文件需要真正保留,不能只留哈希后又覆盖唯一原件。
唯一变化是 Install SSD1306 driver 改成 A07 OLED driver: SSD1306。验证器重算全部1590来源文件,先核I2C块和16k宏,再核这一行;其他意外修改会停止。不要一边换引脚/模型/屏幕,一边把失败归因于这条日志。个性化构建的镜像与记录另存,不覆盖baseline;构建时间等元数据可能让镜像SHA变化,源码恢复不等于保证每次二进制逐字相同。
python tools/source_state.py diff
python tools/source_state.py change
python tools/source_state.py verify-changed
python tools/project.py build
python tools/project.py manifest参考:[9] 固定bread-compact-wifi:显示、按钮、工厂与硬件[13] IDF5.5.5 LCD I2C真实字段顺序
6. 只按当前构建的地址表刷写
保持SD开关断开、功放外供关闭,用主板UART桥USB连接电脑,确认实际串口,再执行:
此处由IDF读取本构建的 flasher_args.json,不是把某个应用bin一律写到0x0。16MB V2分区包含NVS/OTA元数据/PHY、两个约0x3f0000应用槽,以及0x800000处8MB assets区;按生成表核对,不能与N8R8或旧版分区混刷。若转用浏览器,只能使用另行正确合并并核对的完整镜像,不能把app.bin冒充merged-binary.bin。
未保存Wi-Fi的设备应按本版流程进入配网;启动期点BOOT可进入配网,平常点BOOT切换聊天,GPIO47按下/释放调用开始/停止聆听。普通配网激活步骤回看X04,不在这里虚构已登录控制台截图。观察表记录新串口日志、屏幕、版本/配置和后续恢复;本站未完成这些实板观察。
原版会请求默认OTA服务并可能安装更新,联网后的镜像不一定仍是自编版本。本包保留上游行为,不称已禁用OTA。先在配网/离线阶段捕获初始化日志,再联网;出现版本变化或新日志消失,核对是否被OTA替换,使用自己的归档重新刷入。设备信息/音频去向按所用后端说明确认,普通对话并非完全端上处理。
python tools/project.py flash --port /dev/cu.YOUR_BRIDGE_PORT
python tools/project.py monitor --port /dev/cu.YOUR_BRIDGE_PORT参考:[1] S3 DevKitC1 v1.1固定文档:J1/J3、电源与USB[9] 固定bread-compact-wifi:显示、按钮、工厂与硬件[12] 固定OTA:默认服务与版本更新[14] ESP32-S3-WROOM-1/1U v1.8:N16R8规格表
7. 恢复基线与常见失败
restore只把个性化日志恢复到兼容基线,保留两处显式兼容补丁;发现其他编辑不会悄悄抹掉。若需核原样上游,可依次执行remove-compat、verify-upstream;该状态在本板/5.5.5预期会重现字段顺序编译失败,再执行apply-compat同时恢复I2C顺序和功放16k两处兼容基线,不能把这两个阶段混为同一源码。也可使用先前实际保留的完整baseline镜像与对应地址表恢复,二者均须匹配同一配置。只有哈希、没有原文件时,不能声称“恢复原字节”。
| 失败阶段 | 检查与恢复 |
|---|---|
| 组件/SDK约束失败 | 查是否误用v2.5.x、另一个IDF、改过manifest或丢锁;回固定tag和锁,不降低上游要求 |
| I2C designator order硬错 | 查是否移除了本包I2C兼容补丁,核实际5.5.5头文件;按apply-compat恢复,不改SDK或关闭编译错误 |
| 缺lang_config.h | 先运行上游lang_header目标,再build,保留失败日志,不手写生成头 |
| 空间不足/被有界停止 | 停自己的构建,保存原日志和已生成产品;释放已确认可重建的自有缓存后从同输入续建,不动共享SDK |
| 烧录/串口失败 | 核桥接口、数据线、占用;按住BOOT点RESET再松BOOT进入下载;不由此判断音频好坏 |
| 启动反复复位 | 查N16R8实物、16MB Flash/OCT PSRAM、分区/完整地址表和供电;先关闭功放外供 |
| OLED不对/无日志变化 | 核128×32/0x3C/41/42、RST版本、当前镜像及OTA替换;不要先变动音频引脚 |
| 声音/麦克风异常 | 按固定I2S/槽/GAIN/物理SD与外供查,回A01/A02分离底层;本站没有声音成功记录 |
python tools/source_state.py restore
python tools/source_state.py verify-baseline
python tools/project.py build参考:[1] S3 DevKitC1 v1.1固定文档:J1/J3、电源与USB[8] 小智v2.4.0固定manifest:IDF最低约束[9] 固定bread-compact-wifi:显示、按钮、工厂与硬件[12] 固定OTA:默认服务与版本更新[13] IDF5.5.5 LCD I2C真实字段顺序[15] PID4440固定revB PCB:上拉/电平转换/自动复位/128×32
8. 来源、许可和未执行记录
上游完整源保留MIT LICENSE;本包辅助Python/配置说明采用MIT,managed组件和模型依各自原许可,不笼统称全为MIT。主机只核源状态、配置和文件,不能证明Flash物理容量、PSRAM稳定、显示、声音、RF或云端对话。hardware-observations.csv保持表头,读者按实际执行填写;不要用ELF字符串存在替代串口实板日志。
参考:[8] 小智v2.4.0固定manifest:IDF最低约束[10] 固定Simplex codec:使用板配置的采样率与数值转换[11] 固定AudioCodec:默认音量与NVS恢复
本次实际完成的检查与仍空白的物理记录
软件结果来自实际命令、当前源码与原始产物。失败和恢复均保留,四个验证维度分别展示;下表没有把主机夹具替代实体实验。
| 检查项 | 实际结果 | 边界 |
|---|---|---|
| 原样上游 | 真实缺语言头/I2C字段顺序失败,另有空间门限主动停止 | 不是原样上游构建通过 |
| 兼容基线 → 单日志 → 恢复 | 三次实际完整增量构建均Exit0 | 当前三个16k xiaozhi.bin各2629296B;旧24k阶段镜像作为历史保留 |
| ELF / 源状态 | 真实ELF字符串仅在修改版出现新日志;源状态工具实际执行变更与恢复 | ELF字符串存在不是实板串口日志 |
| 实板 / 显示 / 声音 / 后端 | 未执行,空CSV | 没有刷写、激活或对话成功记录 |
参考:[8] 小智v2.4.0固定manifest:IDF最低约束[9] 固定bread-compact-wifi:显示、按钮、工厂与硬件
完整源状态工具
以下逐字对应下载包当前文件;完整依赖、构建说明、许可和空物理记录一起提供。
# ---- tools/source_state.py ----
#!/usr/bin/env python3
# SPDX-License-Identifier: MIT
"""One explicitly bounded log-text change, with whole tracked-tree verification."""
from pathlib import Path
import argparse
import difflib
import hashlib
import json
ROOT = Path(__file__).resolve().parents[1]
REL = "main/boards/bread-compact-wifi/compact_wifi_board.cc"
OLD = 'ESP_LOGI(TAG, "Install SSD1306 driver");'
NEW = 'ESP_LOGI(TAG, "A07 OLED driver: SSD1306");'
def expected_changed(original: str) -> str:
if original.count(OLD) != 1:
raise ValueError("Expected exactly one original log line")
return original.replace(OLD, NEW)
def verify(state: str) -> None:
meta = json.loads((ROOT / "UPSTREAM.json").read_text())
compat = json.loads((ROOT / "COMPATIBILITY.json").read_text())
audio = json.loads((ROOT / "AUDIO-COMPATIBILITY.json").read_text())
for rel, expected_sha in meta["files"].items():
data = (ROOT / "firmware" / rel).read_bytes()
if state == "changed" and rel == REL:
text = data.decode()
if text.count(NEW) != 1 or OLD in text:
raise ValueError("Selected file is not the single known change")
data = text.replace(NEW, OLD).encode()
if state != "upstream" and rel == REL:
text = data.decode()
if text.count(compat["patchedBlock"]) != 1:
raise ValueError("Expected explicit IDF5.5.5 compatibility patch")
data = text.replace(compat["patchedBlock"], compat["originalBlock"]).encode()
if state != "upstream" and rel == audio["relativePath"]:
text = data.decode()
if text.count(audio["patchedLine"]) != 1:
raise ValueError("Expected explicit MAX98357A supported16k output patch")
data = text.replace(audio["patchedLine"], audio["originalLine"]).encode()
if hashlib.sha256(data).hexdigest() != expected_sha:
raise ValueError(f"Unexpected tracked source modification: {rel}")
print(f"PASS {len(meta['files'])} original tracked files, state={state}")
def main() -> None:
p = argparse.ArgumentParser(description=__doc__)
p.add_argument("action", choices=["verify-upstream", "apply-compat", "remove-compat", "verify-baseline", "change", "verify-changed", "restore", "diff"])
args = p.parse_args()
target = ROOT / "firmware" / REL
if args.action in ["verify-upstream", "apply-compat"]:
verify("upstream")
elif args.action in ["verify-baseline", "change", "diff", "remove-compat"]:
verify("baseline")
else:
verify("changed")
if args.action in ["apply-compat", "remove-compat"]:
compat = json.loads((ROOT / "COMPATIBILITY.json").read_text())
audio = json.loads((ROOT / "AUDIO-COMPATIBILITY.json").read_text())
before, after = compat["originalBlock"], compat["patchedBlock"]
if args.action == "remove-compat":
before, after = after, before
text = target.read_text()
if text.count(before) != 1:
raise ValueError("Compatibility block differs; no file changed")
audio_target = ROOT / "firmware" / audio["relativePath"]
audio_before, audio_after = audio["originalLine"], audio["patchedLine"]
if args.action == "remove-compat":
audio_before, audio_after = audio_after, audio_before
audio_text = audio_target.read_text()
if audio_text.count(audio_before) != 1:
raise ValueError("Audio compatibility line differs; no file changed")
target.write_text(text.replace(before, after))
audio_target.write_text(audio_text.replace(audio_before, audio_after))
verify("baseline" if args.action == "apply-compat" else "upstream")
elif args.action == "change":
target.write_text(expected_changed(target.read_text()))
verify("changed")
elif args.action == "restore":
target.write_text(target.read_text().replace(NEW, OLD))
verify("baseline")
elif args.action == "diff":
original = target.read_text()
print("".join(difflib.unified_diff(original.splitlines(True), expected_changed(original).splitlines(True), fromfile="a/"+REL, tofile="b/"+REL)), end="")
if __name__ == "__main__":
main()参考:[8] 小智v2.4.0固定manifest:IDF最低约束[13] IDF5.5.5 LCD I2C真实字段顺序
完整构建与当前镜像追踪工具
以下逐字对应下载包当前文件;完整依赖、构建说明、许可和空物理记录一起提供。
# ---- tools/project.py ----
#!/usr/bin/env python3
# SPDX-License-Identifier: MIT
"""Use an already activated IDF5.5.5; record inputs and refuse stale-image manifests."""
from pathlib import Path
import argparse
import datetime
import hashlib
import json
import os
import subprocess
import sys
ROOT = Path(__file__).resolve().parents[1]
BUILD = ROOT / 'build'
SOURCE = ROOT / 'firmware'
def fingerprint(path: Path) -> dict:
return {'path': str(path.resolve()), 'bytes': path.stat().st_size,
'sha256': hashlib.sha256(path.read_bytes()).hexdigest()}
def inputs() -> list[dict]:
# Includes generated language header and the resolver lock when present.
files = [p for p in SOURCE.rglob('*') if p.is_file() and
'managed_components' not in p.parts and '__pycache__' not in p.parts]
files += [ROOT/'configs/lab.defaults', BUILD/'sdkconfig']
if any(not p.is_file() for p in files):
raise SystemExit('Configure first; missing configuration input')
return [fingerprint(p) for p in sorted(set(files))]
def products() -> list[dict]:
files = [p for p in BUILD.rglob('*') if p.is_file() and
(p.suffix in ['.bin', '.elf', '.map'] or p.name in
['sdkconfig', 'flasher_args.json', 'flash_args', 'project_description.json',
'compile_commands.json'])]
return [fingerprint(p) for p in sorted(files)]
def run(command: list[str], label: str) -> dict:
logs = ROOT/'local-logs'
logs.mkdir(exist_ok=True)
stamp = datetime.datetime.now(datetime.timezone.utc).strftime('%Y%m%dT%H%M%S%fZ')
output = logs/f'{stamp}-{label}.log'
with output.open('wb') as stream:
stream.write(('COMMAND '+json.dumps(command)+'\n').encode())
stream.flush()
process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
assert process.stdout is not None
for line in process.stdout:
stream.write(line)
stream.flush()
sys.stdout.buffer.write(line)
sys.stdout.buffer.flush()
code = process.wait()
stream.write(f'\nEXIT {code}\n'.encode())
record = {'command': command, 'exit': code, 'log': fingerprint(output)}
output.with_suffix('.json').write_text(json.dumps(record, indent=2))
if code:
raise SystemExit(code)
return record
def current_success() -> dict:
record = ROOT/'local-last-success.json'
if not record.is_file():
raise SystemExit('No successful build recorded by this tool; existing bins are insufficient')
value = json.loads(record.read_text())
if value['inputs'] != inputs() or value['products'] != products():
raise SystemExit('Inputs/products changed since the recorded build; rebuild before manifest/flash')
for step in value['commands']:
log = Path(step['log']['path'])
if fingerprint(log) != step['log']:
raise SystemExit('Recorded build log missing or changed')
return value
def main() -> None:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('action', choices=['configure', 'build', 'manifest', 'flash', 'monitor'])
parser.add_argument('--port')
args = parser.parse_args()
sdk = os.environ.get('IDF_PATH')
if not sdk or not (Path(sdk)/'tools/idf.py').is_file():
raise SystemExit('Activate ESP-IDF5.5.5 first; this tool does not install an SDK')
idf = [sys.executable, str(Path(sdk)/'tools/idf.py')]
version = subprocess.check_output(idf+['--version'], text=True).strip()
if version != 'ESP-IDF v5.5.5':
raise SystemExit('Wrong SDK: '+version+'; expected ESP-IDF v5.5.5')
base = idf+['-C', str(SOURCE.resolve()), '-B', str(BUILD.resolve()),
'-D', 'SDKCONFIG='+str((BUILD/'sdkconfig').resolve())]
if args.action == 'configure':
run(base+['-D', 'IDF_TARGET=esp32s3', '-D',
'SDKCONFIG_DEFAULTS='+str(SOURCE/'sdkconfig.defaults')+';'+str(ROOT/'configs/lab.defaults'),
'reconfigure'], 'configure')
elif args.action == 'build':
# Fixed upstream ALL target lacks an explicit main-component dependency.
language = run(base+['lang_header'], 'lang-header')
before = inputs()
build = run(base+['build'], 'build')
if inputs() != before:
raise SystemExit('Inputs changed during the build; no success record emitted')
result = products()
if not any(Path(p['path']).name == 'xiaozhi.bin' for p in result):
raise SystemExit('Expected application image missing')
(ROOT/'local-last-success.json').write_text(json.dumps({
'version': version, 'inputs': before, 'products': result,
'commands': [language, build], 'hardware': 'not executed by this tool'}, indent=2))
elif args.action == 'manifest':
value = current_success()
value['upstream'] = json.loads((ROOT/'UPSTREAM.json').read_text())['commit']
(ROOT/'local-products.json').write_text(json.dumps(value, indent=2))
print(ROOT/'local-products.json')
else:
if not args.port:
raise SystemExit('Provide the physical USB-UART bridge --port')
if args.action == 'flash':
current_success()
run(base+['-p', args.port, args.action], args.action)
if __name__ == '__main__':
main()参考:[8] 小智v2.4.0固定manifest:IDF最低约束[13] IDF5.5.5 LCD I2C真实字段顺序
资料来源与版本说明
固定上游、实际SDK/managed和厂家具体电路章节已核;主机/构建记录不替代指定硬件、HA/云端、光学或声音实测。资料核对日期:。资料核对、程序构建与实板测试分别记录,不互相替代。
- S3 DevKitC1 v1.1固定文档:J1/J3、电源与USB
- ICS43434 DS-000069 v1.2:电气/边沿/24bit/启动
- PID6049固定PCB:3V、SEL与47k数据下拉
- MAX98357A/MAX98357B Rev7:供电/采样率/SD/GAIN/BTL
- PID3006固定单声道PCB:VIN/SD1M/BTL端子
- PID1313扬声器2017-02-14厂家规格:8Ω额定1W
- PID6049产品页:具体型号与停产边界
- 小智v2.4.0固定manifest:IDF最低约束
- 固定bread-compact-wifi:显示、按钮、工厂与硬件
- 固定Simplex codec:使用板配置的采样率与数值转换
- 固定AudioCodec:默认音量与NVS恢复
- 固定OTA:默认服务与版本更新
- IDF5.5.5 LCD I2C真实字段顺序
- ESP32-S3-WROOM-1/1U v1.8:N16R8规格表
- PID4440固定revB PCB:上拉/电平转换/自动复位/128×32
- Adafruit PID4440:具体尺寸、自动复位版本
- 固定board config:真实I2S/按钮/GPIO48/18
- 固定CMake:语言头生成目标与组件选择
- 小智v2.5.2 manifest:>=6.0.1差异
- 固定AudioService:decoder采样率与输出重采样路径
- 实际esp_audio_effects1.3.0~1来源:rate converter格式/采样率/API约束