第1章 克隆并运行智能声音识别通知器
本章从一个已经形成完整链路的教学项目开始。项目使用麦克风采集声音。嵌入式设备完成音频特征计算和模型推理,再根据事件规则决定是否发送通知。
声音输入
→ 音频采样
→ 特征计算
→ 端侧模型推理
→ 事件规则
→ 网络通知
本章不修改代码,也不使用AI工具。学习重点有两个。第一,观察物理AI产品从输入到输出的过程。第二,使用Git确认项目、版本及运行前后的仓库状态。
完成本章后,应当能够:
- 从校内GitLab克隆课程教学仓库;
- 识别远程地址、当前分支、提交号和工作区状态;
- 按课程环境卡完成构建、烧录和串口观察;
- 区分模型输出、事件判断和用户通知;
- 形成包含版本信息和实际现象的运行记录;
- 说明为什么“能够运行”必须与确定的仓库版本对应。
1.1 从课程GitLab取得项目
1.1.1 克隆前确认四项信息
教师在课程平台发布“第1章环境卡”。环境卡至少包含:
| 项目 | 作用 |
|---|---|
| 课程仓库地址 | 确定代码来源 |
| 起始标签 | 确定本章使用的稳定版本 |
| 支持的开发板与麦克风 | 确定硬件条件 |
| 构建工具及版本 | 确定编译环境 |
教材用下面的地址形式表示校内仓库:
https://<课程GitLab地址>/physical-ai/sound-event-terminal.git
实际地址以当学期环境卡为准。不要根据仓库名称在互联网中自行寻找同名工程,因为同名工程可能具有不同代码、依赖和许可证。
在本地选择一个专门保存课程项目的目录。Windows路径应避免过长。仓库不宜放在桌面临时目录、同步网盘或含有大量特殊字符的路径中。例如:
D:\course\s1\
打开PowerShell或VS Code终端,先进入该目录:
Set-Location D:\course\s1
再执行克隆:
git clone https://<课程GitLab地址>/physical-ai/sound-event-terminal.git
克隆完成后进入仓库:
cd sound-event-terminal
1.1.2 git clone取得了什么
克隆不是普通的文件下载。Git在本地建立了三类内容:
- 当前版本的项目文件;
- 项目已有的提交历史、标签和分支信息;
- 指向校内GitLab仓库的远程地址。
因此,本地仓库能够回答三个问题。当前文件来自哪次提交,参数何时改变,哪些修改尚未提交。网页下载的压缩包只能保存某一时刻的文件,不能直接保留这些工程关系。
早期的小型项目常用复制文件夹的方式保存版本。项目扩大后,副本之间很难比较,成员也难以判断哪一份可以继续开发。集中式版本控制把历史统一保存在服务器中,但离线时能够执行的操作受到限制。Git采用分布式版本控制,每次克隆都取得项目历史,并能在本地比较、提交和建立分支。
这种方式提高了离线工作和故障恢复能力,也带来相应代价。完整历史会占用存储空间,分支和合并也要求使用者理解提交关系。本课程保留这些必要复杂度,因为AI生成的变化同样需要可靠的来源、边界和历史。
执行:
git remote -v
典型输出如下:
origin https://<课程GitLab地址>/physical-ai/sound-event-terminal.git (fetch)
origin https://<课程GitLab地址>/physical-ai/sound-event-terminal.git (push)
origin是本地为远程仓库保存的简称。fetch表示从哪里取得更新,push表示向哪里推送。本章只读取课程仓库,不执行推送。
如果输出的地址不是环境卡给出的校内地址,应暂停操作并检查是否进入了错误目录。
1.2 固定本章使用的版本
1.2.1 切换到稳定标签
课程教学不能直接依赖持续变化的主分支。教师应为第1章发布稳定标签,例如:
sound-lab-v0.1-baseline
先取得远程标签:
git fetch --tags
再切换到本章标签:
git switch --detach sound-lab-v0.1-baseline
这里的“分离状态”表示当前检出的是一个确定的提交,而不是准备继续提交的新分支。第1章只运行基线,使用分离状态能够避免误把实验修改提交到错误分支。第2章开始修改时,再从该标签建立个人练习分支。
检查当前标签和提交:
git describe --tags --always
git rev-parse HEAD
第一条命令给出便于阅读的标签,第二条命令给出完整提交号。提交号是一串由Git计算的标识,用于精确指向项目状态。运行记录中应保存完整提交号,截图只显示前几位时,还需要在文字记录中补全。
1.2.2 检查工作区
执行:
git status --short
刚克隆并切换标签后,这条命令通常没有输出。没有输出表示当前工作区中的受控文件与该提交一致。
如果出现内容,应先识别状态符号:
| 示例 | 含义 |
|---|---|
M firmware/main.cpp |
已跟踪文件在工作区被修改 |
M firmware/main.cpp |
修改已经进入暂存区 |
?? build.log |
出现尚未被Git跟踪的新文件 |
D docs/setup.md |
删除已经进入暂存区 |
在没有理解原因前,不要继续编译,更不要执行清理命令。常见原因包括进入了上一次实验目录、编辑器自动格式化了文件,或者构建产物没有被正确忽略。
1.2.3 建立项目身份记录
运行前在实验记录中填写:
仓库地址:
标签:
完整提交号:
当前分支或检出状态:
工作区是否干净:
记录时间:
这组信息构成运行结果的“版本身份”。同一段程序在依赖、板卡或模型变化后可能产生不同结果,仅记录“运行成功”无法支持复现。
1.3 阅读仓库的入口文件
1.3.1 先读README.md
打开项目根目录的README.md。课程仓库的README.md应回答:
- 项目解决什么问题;
- 支持哪些硬件和工具链;
- 怎样检查环境;
- 怎样构建、烧录和查看日志;
- 哪些文件包含本地凭据;
- 当前版本有哪些已知限制。
若README.md中的命令与环境卡不一致,以课程组发布的勘误和当学期环境卡为准,并在实验记录中注明差异。不要混用不同版本的安装说明。
1.3.2 识别项目目录
课程教学仓库使用下面的基本结构:
sound-event-terminal/
├─ README.md
├─ LICENSE
├─ CHANGELOG.md
├─ docs/
│ ├─ architecture.md
│ ├─ course-environment.md
│ └─ verification.md
├─ firmware/
│ ├─ sound_event_terminal.ino
│ ├─ audio_frontend.h
│ ├─ model_runner.h
│ ├─ model_data.h
│ ├─ event_policy.h
│ ├─ notification_client.h
│ └─ secrets.example.h
├─ models/
│ └─ model-manifest.json
├─ samples/
│ └─ manifest.csv
└─ tools/
└─ course.py
目录名称体现工程对象的职责:
firmware/保存设备端程序;models/记录模型文件的版本、输入输出和散列值;samples/记录允许用于课程验证的音频样本及其许可;docs/保存架构、环境和验证说明;tools/提供统一的环境检查和构建入口。
模型数据可能以C/C++头文件形式进入固件,也可能由构建过程转换后嵌入。无论采用哪种方式,模型清单必须能够指出模型文件、版本、散列值和输出类别。第9章将专门学习模型怎样作为工程构建物进入仓库。
1.3.3 找到程序主链路
在 sound_event_terminal.ino 中查找以下四类调用:
audioFrontend.read(...);
audioFrontend.writeFeatures(...);
modelRunner.predict(...);
eventPolicy.update(...);
具体函数名可能随课程版本调整,但职责保持不变:
- 从音频外设取得脉冲编码调制(Pulse Code Modulation,PCM)采样;
- 将声音转换为模型所需特征;
- 运行端侧模型并取得各类别输出;
- 根据平滑、阈值和冷却策略形成事件。
通知客户端只接收已经形成的事件,不参与模型推理。把这些职责分开,有利于后续分别验证声音输入、模型、事件规则和网络服务。
1.4 准备构建环境
1.4.1 使用统一入口检查环境
课程仓库使用统一脚本检查本机环境:
python tools/course.py doctor
该命令应检查:
- Git是否可用;
- Python版本是否符合要求;
- 嵌入式构建工具是否安装;
- 课程板卡支持包是否存在;
- 必需库的版本是否匹配;
- 串口工具是否可用;
- 本地密钥文件是否被Git忽略。
检查结果分为:
OK 已满足
WARN 可以继续,但需要注意
FAIL 必须先解决
出现 FAIL 时,先按输出提示处理。不要通过修改检查脚本把失败改成通过;检查脚本本身属于课程基线的一部分。
1.4.2 工具版本为什么需要记录
嵌入式工程的构建结果不仅由源代码决定,还受到编译器、板卡包、库和链接参数影响。环境卡应记录类似信息:
构建工具:
板卡支持包:
音频处理库:
端侧推理库:
网络客户端库:
纸质教材不固定容易变化的版本号,当学期实际版本由环境卡给出。运行记录应保存环境检查输出,使以后能够区分“代码变化”和“工具链变化”。
1.4.3 凭据文件不进入仓库
网络通知通常需要Wi-Fi参数和服务凭据。课程仓库只提供示例:
firmware/secrets.example.h
学生复制为本地文件:
Copy-Item firmware\secrets.example.h firmware\secrets.local.h
填写前先检查它是否被Git忽略:
git check-ignore -v firmware/secrets.local.h
命令应显示是哪一条忽略规则生效。如果没有输出,说明文件没有被忽略,应先停止填写并报告教师。
密钥文件即使被Git忽略,也不应出现在截图、AI对话、实验报告或公开日志中。.gitignore只负责Git是否跟踪文件,不等于访问权限控制。
1.5 连接设备并完成第一次构建
1.5.1 按环境卡连接硬件
开发板、麦克风类型和引脚连接可能随学期调整,因此纸质教材只规定检查原则:
- 确认供电电压与麦克风模块要求一致;
- 确认地线连接;
- 确认模拟麦克风输入、PDM或I²S接口类型;
- 在上电前复核信号脚;
- 使用系统识别到的实际串口;
- 不在设备通电时随意改变接线。
具体接线图放在在线板卡适配说明中。若使用的硬件与环境卡不一致,应先完成适配验证,不能仅替换板卡名称后继续。
1.5.2 构建固件
执行:
python tools/course.py build
统一入口会调用课程规定的嵌入式构建工具。构建成功证明以下事项:
- 当前源代码通过编译;
- 依赖能够被找到;
- 目标板卡配置与代码兼容;
- 固件产物已经生成。
构建成功尚不能证明麦克风能够采样、模型能够产生正确输出或通知服务已经接通。编译属于静态构建证据,设备运行属于另一类证据。
记录构建输出中的:
- 目标板卡;
- 固件大小;
- 随机存取存储器(Random Access Memory,RAM)或静态内存使用量;
- 构建时间;
- 生成产物位置。
若构建失败,从第一条有效错误开始定位。后续错误常常由第一条错误连锁产生,同时修改多个文件会扩大问题范围。
1.5.3 烧录并打开串口
先列出可用串口:
python tools/course.py ports
假设课程开发板位于 COM5,执行:
python tools/course.py flash --port COM5
python tools/course.py monitor --port COM5
串口号必须使用本机实际值。烧录成功表示固件已经写入目标设备;仍需要观察启动日志,确认各模块完成初始化。
启动日志至少应能够区分:
[BOOT] firmware=<版本>
[AUDIO] ready
[MODEL] ready, version=<模型版本>
[NET] connected
[APP] listening
课程实现可以使用不同格式,但日志应包含模块名称和明确状态。只有一行“启动成功”不利于定位失败边界。
1.6 用真实输入观察完整链路
1.6.1 准备正例和反例
本章至少准备两类输入:
- 正例:模型清单中指定的目标声音;
- 反例:与目标类别不同的环境声、人声或音乐。
音频来源应符合课程样本清单。自采声音需要记录采集设备、距离、音量和环境;外部音频需要记录来源和许可。不得把来源不明的音频直接放入公开仓库。
1.6.2 观察模型输出
在规定距离和音量下播放正例,观察串口。课程固件应能够输出类似结构:
[INFER] class=<类别> score=<实际值> model=<模型版本>
[POLICY] smoothed=<实际值> threshold=<配置值> cooldown=<状态>
实际数值由设备运行产生,不应事先填写。记录时需要区分:
score:模型对当前输入的输出;smoothed:事件规则处理后的连续值;threshold:产品当前使用的触发阈值;cooldown:当前是否处于通知冷却期。
模型输出超过某个阈值,并不等于模型整体准确率达到同一个百分比。模型准确率、召回率和混淆矩阵由《人工智能数据科学》课程在规定测试集上评价;本章只观察一次输入经过产品链路后的实际行为。
1.6.3 观察事件和通知
当事件规则满足时,串口应继续输出:
[EVENT] type=<事件类型> id=<事件编号>
[NOTIFY] status=<状态码>
同时检查课程通知页面或客户端是否出现对应事件。完整链路至少需要三类证据:
- 模型或事件规则日志;
- 设备发送结果;
- 服务端或用户端收到的事件。
只看到用户页面出现通知,不能证明通知由本次设备推理触发;只看到模型输出,也不能证明网络和服务已经工作。事件编号或时间戳用于把设备日志与服务端记录对应起来。
1.6.4 使用反例检查边界
播放反例并记录:
- 模型输出类别和数值;
- 事件规则是否触发;
- 是否发送通知。
反例没有通知,可能是模型输出较低,也可能是平滑、阈值或冷却规则阻止了事件。运行记录应指出观察到了哪一层,不能只写“反例测试通过”。
1.7 理解产品中的六个工程边界
智能声音识别通知器可以分为六个工程部分:
| 部分 | 当前作用 | 深入学习课程 |
|---|---|---|
| 感知 | 麦克风取得声音信号 | 《感知与异构计算系统原型》 |
| 数据 | PCM采样、窗口和特征张量 | 两门协同课程 |
| 模型 | 输出声音类别分数 | 《人工智能数据科学》 |
| 事件规则 | 平滑、阈值、连续确认和冷却 | 本书关注产品集成 |
| 通信服务 | 将结构化事件发送到服务器 | 本书关注接口和可靠性 |
| 用户反馈 | 显示或推送通知 | 本书关注验收和发布 |
本书不会在本章推导Mel频谱,也不训练声音分类模型。我们需要理解各部分传递的数据及其实现文件。还要理解变化的验证方法,以及这些变化怎样进入Git历史。
从第5章开始,教师贯穿项目“智能感知终端”会复用同样的结构:
传感器输入
→ 数据窗口
→ 模型或规则判断
→ 结构化事件
→ 网络服务
→ 用户反馈
项目形态可以变化,工程边界和协作方法保持稳定。
1.8 运行结束后再次检查Git
关闭串口工具后执行:
git status --short
理想情况下仍然没有输出。构建和运行产生的固件、缓存、串口日志和本地凭据应当被合理忽略,不应污染工作区。
如果出现新文件,先判断其性质:
| 文件类型 | 处理原则 |
|---|---|
| 可重新生成的构建产物 | 加入适当的忽略规则,由课程组统一修正 |
| 需要保存的测试证据 | 放入规定的证据目录,并在后续分支提交 |
| 本地凭据和个人配置 | 保持本地且必须忽略 |
| 源代码意外修改 | 查明编辑器或工具行为,恢复基线 |
本章处于分离检出状态,不提交运行记录到课程基线。运行记录由课程平台收集,或保存在个人学习仓库中。第2章建立个人练习分支后,再开始提交项目变化。
1.9 编写第一次运行记录
运行记录建议采用下面的结构:
# 第1章运行记录
## 版本
- 仓库:
- 标签:
- 提交号:
## 环境
- 开发板:
- 麦克风:
- 构建工具:
- 模型版本:
## 输入
- 正例来源与条件:
- 反例来源与条件:
## 结果
- 构建:
- 烧录:
- 模型输出:
- 事件判断:
- 通知:
## 未通过项目
- 现象:
- 已定位到的边界:
- 下一步检查:
“未通过项目”允许为空,但不能隐去失败。真实工程记录的价值在于保留可复查事实,而不是把每次实验整理成全部成功的展示材料。
1.10 常见问题
克隆时提示认证失败
检查是否使用了课程支持的HTTPS或SSH方式、账号是否具有仓库读取权限。不要把个人访问令牌写进命令历史或截图。
找不到本章标签
先执行 git fetch --tags,再检查标签拼写。仍不存在时,核对仓库远程地址。
环境检查通过但构建失败
保存第一条有效错误、工具版本和当前提交号。不要立即升级所有依赖;课程项目依赖固定版本,升级可能引入新的不兼容。
烧录成功但没有串口输出
检查串口号、波特率、复位时机和串口是否被其他程序占用。烧录端口与运行日志端口在部分开发板上可能不同。
有音频输入但没有模型输出
依次检查音频初始化、采样数据、特征缓冲、模型初始化和推理调用。一次只判断一个边界。
有事件日志但用户端没有通知
检查设备网络状态、请求状态码、服务端事件记录和用户端显示。模型无需重新训练,因为问题已经发生在事件形成之后。
1.11 本章小结
本章完成了智能声音识别通知器基线的取得和运行。Git在这一过程中承担了三个基本作用:
- 通过远程地址说明项目来自哪里;
- 通过标签和提交号说明运行的是哪一个版本;
- 通过工作区状态说明运行前后是否产生了未预期变化。
学生已经观察到一个物理AI产品的完整链路,也形成了第一份带有版本身份的运行记录。第2章将在同一基线上建立个人练习分支。学生将手工修改一项事件规则,经差异检查和实机验证后完成第一次Git提交。
1.12 综合实践
- 为课程仓库编写一份“项目身份卡”。身份卡应包含远程地址、标签、提交号、工作区状态和工具链信息,并说明任意一项变化可能造成的影响。
- 自行选择一组符合许可要求的正例和反例,设计证据矩阵。矩阵分别记录模型输出、事件判断和通知结果,并说明三类证据不能相互替代的原因。
- 根据一次实际运行记录,画出项目数据流。再选择一个未通过现象,提出两个来自不同工程边界的原因,并写出区分它们所需的证据。
- 选择一个构建后产生的文件,判断它应被忽略、作为证据保存,还是作为源文件纳入仓库。提交一份包含判断依据的处理建议。