33 嵌入式操作系统 | 模块七验收:端到端一键验收 + 项目答辩

嵌入式操作系统 | 模块七验收:端到端一键验收 + 项目答辩

本课程开源地址(Gitee) :https://gitee.com/fujianxinxi/qianrushixitongyingyongkaifa.git

课件、示例代码与验收脚本都在该仓库,可直接 git clone 或下载 ZIP 使用。
模块七 · 2 学时 · 环境:Ubuntu(完整项目环境)+ socat 虚拟串口

一句话概述 :这节课不写新功能,只做两件事------① 用 acceptance.sh 一键把整条链路重跑一遍 ,逐项打勾(本节实测 27 项全通过 );② 按《验收清单》自评、按《答辩提纲》演练 5 分钟讲解。学完这节,你手上有一个能演示、能交付、能自愈的小系统。

本节目标

  1. 会用"一条命令 + 一个现象 "的方式证明每个功能点(这就是回归测试的思路)
  2. 能独立跑通 acceptance.sh,看懂每一项检查在验什么、失败时怎么查
  3. 会按《验收清单》给自己打分(A 功能 60 / B 健壮性 20 / C 工程化 12 / D 交付答辩 8)
  4. 能按《答辩提纲》讲清架构、演示主流程、回答原理题

一、讲解内容

1.1 知识讲解

① 验收和"我觉得能跑"的区别. 验收的核心是可复现的证据:

不行的说法 验收要的写法
"网页能控制设备" 执行 wget -qO- 'http://127.0.0.1:5000/cmd?name=led_on',4 秒后 ubus call device status 里 "led": 1
"掉线能发现" 杀掉面板,6 秒后 /api/state 里 "online":0;重启面板 6 秒后变回 1
"挺健壮的" 灌一帧坏 CRC,网关日志出现"串口帧 CRC 失败...丢弃",之后 ser_rx 继续增长

② acceptance.sh 的设计思路(十项,27 个检查点)

段 检查什么 用到的组件
① 环境:socat / libubox / libubus / Flask / pyserial / uci+ubus / systemd 课时 15、24
② 虚拟串口建立 socat
③ 设备面板在发帧 + 网关起来了 + 认到设备 课时 26、31
④ 状态表正确(online=1、ser_rx>0) 课时 29、31(ubus)
⑤ 每 2 秒 0x81 上报 课时 29
⑥ 网页显示在线 + 点按钮能改状态 课时 30
⑦ 掉线判 0 / 恢复判 1 课时 29、30
⑧ 灌坏帧后照常工作 课时 32
⑨ systemd 服务化 + kill -9 自愈 课时 32
⑩ 清理现场(进程/端口/服务) 全部

脚本的骨架非常朴素------一个计数器 + 两种打印:

bash 复制代码
PASS=0; FAIL=0
ok()  { echo "  [通过] $1"; PASS=$((PASS + 1)); }
bad() { echo "  [失败] $1"; FAIL=$((FAIL + 1)); }

command -v socat > /dev/null && ok "socat 已安装" || bad "socat 未安装"

最后:if [ "$FAIL" -eq 0 ]; then echo "验收通过"; else echo "有 $FAIL 项没过"; fi

③ 为什么必须"一键"? 十个检查点手工做一遍要十几分钟,而且容易漏;写成脚本以后:每次改代码都重跑一遍 (回归测试),改坏了立刻能发现是哪一项坏了。这也解释了课时 32 为什么要搞故障注入------验收里必须包含"坏情况"。

④ 验收清单(100 分)与答辩(5 分钟) 见同目录两份文件:

  • 代码/课时33-项目验收/验收清单.md:A 功能 60 分、B 健壮性 20 分、C 工程化 12 分、D 交付答辩 8 分,每项都写了"怎么证明"
  • 代码/课时33-项目验收/答辩提纲.md:5 分钟时间分配 + 12 个常问问题 + 加分/扣分项

⑤ 整个模块七做出来的东西(复习地图)

课时 做了什么 关键文件
24 环境 + 协议契约 check_env.py、hello_qt.py、hello_flask.py、协议契约.md
25 设备面板界面(PyQt5) device_panel.py / device_panel_full.py
26 面板接串口(半帧/CRC/断线重连) device_panel_serial.py、serial_monitor.py
27 网关雏形:串口 ⇄ UDP gateway.c、udp_client.py
28 网关 TCP 命令通道(粘包/半包/多客户端) gateway_tcp.c、tcp_client.py
29 状态表 + 每 2 秒上报 + 在线判定 gateway_full.c、state_listener.py
30 网页上位机(显示 + 控制闭环) app.py
31 产品化:配置读 uci + 状态走 ubus gateway_pro.c、device.conf、ubus_query.sh
32 可靠性:故障注入 + systemd 自愈 fault_device.py、fault_tcp.py、gateway.service、install_service.sh
33 验收:一键端到端 + 答辩 acceptance.sh、验收清单.md、答辩提纲.md

1.2 操作步骤(一条命令一步,看现象)

实测环境是在 代码/ 目录下(脚本里的路径指向各课时目录);同学们按自己的目录改路径即可。

第 0 步:先把"总机"和网关程序准备好

shell 复制代码
pgrep -x ubusd > /dev/null || sudo ubusd &         # ubus 总机(课时 31 用过)
ls ~/lab/课时31-网关产品化uci与ubus/gateway_pro      # 网关程序(课时 32 守护的就是它)
uci show device | head -3                          # 配置在位

第 1 步:★跑一键验收(约 1~2 分钟)

shell 复制代码
cd ~/lab/课时33-项目验收
sudo ./acceptance.sh            # 想快点:./acceptance.sh --quick(跳过完整故障注入)

现象(实测,27 项全通过、0 失败):

text 复制代码
================ 模块七 综合项目 验收 ================
时间:2026-09-26 06:04:04

【① 环境检查】
  [通过] socat 已安装
  [通过] libubox 头文件在位
  [通过] libubus 头文件在位
  [通过] Flask 可用
  [通过] pyserial 可用
  [通过] uci / ubus 可用
  [通过] systemd 可用

【② 建立虚拟串口】
  [通过] 虚拟串口 /tmp/ttyV0 <-> /tmp/ttyV1 已建立

【③ 设备面板 + 网关】
  [通过] 设备面板在发 0x80 状态帧
  [通过] 网关已启动(配置读 /etc/config/device)
  [通过] 网关认到了设备(状态表已建立)

【④ 网关状态查询(ubus)】
  [通过] ubus call device status 有返回
  [通过] online = 1(设备在线)
  [通过] ser_rx = 3(串口确实收到过帧)

【⑤ 状态上报(每 2 秒 0x81)】
  [通过] 监听端 5 秒内收到 3 帧 0x81 上报
       收到 55 AA 81 05 00 00 00 F7 01 0D AD  →  灯 灭 | 风扇 0 档 | 24.7 ℃ | 在线

【⑥ 网页上位机(显示 + 控制)】
  [通过] /api/state 显示设备在线
  [通过] 网页按钮控制成功(led=1 fan=2)

【⑦ 设备掉线判定与恢复】
  [通过] 设备掉线后 online 变成 0
  [通过] 设备回来后 online 自动恢复成 1

【⑧ 故障注入(坏帧不影响正常工作)】
  [通过] 网关识别并丢弃了坏 CRC 帧
  [通过] 故障注入后网关照常收帧(ser_rx 25 → 50)
  [通过] 故障注入后设备仍在线

【⑨ systemd 守护与自愈】
  [通过] 服务已启动(systemctl is-active = active)
  [通过] kill -9 后服务被自动拉起(active)
  [通过] 重启次数 NRestarts = 2
  [通过] 服务已卸载(现场恢复干净)

【⑩ 清理现场】
  [通过] 所有进程已停止,端口已释放

================ 验收结果 ================
通过:27 项    失败:0 项
结论:模块七 综合项目 验收通过 ✓

第 2 步:按《验收清单》给自己打分

shell 复制代码
less ~/lab/课时33-项目验收/验收清单.md

逐条对照,把"A 功能 60 / B 健壮性 20 / C 工程化 12 / D 交付答辩 8"填完。

acceptance.sh 覆盖了其中绝大部分可自动化的项,剩下的(代码整洁、README、答辩)由老师和同学互评。

第 3 步:★换一个终端/新环境再跑一遍(真正的验收)

shell 复制代码
# 换一个干净终端,或者在新克隆的目录里,按 README 从头跑一遍
cd ~/lab/课时33-项目验收
sudo ./acceptance.sh

这一步的意义:证明你的项目不是"只能在你机器上跑" 。

常见翻车点:路径写死、忘了装某个包、忘了 uci commit、忘了起 ubusd。

第 4 步:答辩演练(5 分钟掐表)

shell 复制代码
less ~/lab/课时33-项目验收/答辩提纲.md

按提纲的四段做一遍:① 架构图 1 分钟 → ② 主流程演示 2 分钟 → ③ 掉线 + 自愈 1 分钟 → ④ 坑与收获 1 分钟。

再让同桌随机问"12 个常问问题"里的 3 个,答不上来的回去补。

第 5 步:收工。 脚本已自动清理(进程/端口/服务都还原);ubusd 可留可停(sudo pkill -x ubusd)。

1.3 常见错误与排查

现象 原因 解决
④ 报 ubus 查询没返回 ubusd 没启动 sudo ubusd &;或 ls -l /var/run/ubus/ubus.sock 确认
③ 报 设备面板没有发帧 面板没起来(PyQt/Xvfb 问题) 看 /tmp/acc_panel.log;无桌面时用 xvfb-run python3 device_panel_serial.py
③ 报 网关没起来 9000 端口被占 / 串口打不开 `ss -ltnp
⑤ 报 5 秒内没收到上报 网关的上报目标不是 127.0.0.1:9100 看 /tmp/acc_gw.log 的启动行;或 `uci show device
⑥ 报 /api/state 不对 5000 被占 / 网页没起来 看 /tmp/acc_app.log;`ss -ltnp
⑦ 掉线判不出来 offline_ms 配得太大 uci set device.main.offline_ms=3000 && uci commit device 后重启网关
⑨ 服务 self-heal 失败 systemd 不可用(容器里常见) 用虚拟机跑;或在真机上确认 PID 1 是 systemd
脚本跑完端口还被占 上一次跑挂了没清理 pkill -f gateway_pro; pkill -x socat,或重启虚拟机
全部失败但手工跑却正常 目录不对(脚本找各课时目录找错了) 脚本用 $0 自动算出 C26/C29/C30/C31/C32(从"脚本所在目录"往上两层取 代码/),仓库 clone 或解压到哪都能跑;只有你把某个课时目录搬走时才需要改开头的这几行

二、学生练习

本节命令速查表:

命令 作用 示例
sudo ./acceptance.sh 一键端到端验收(十项 27 检查点) --quick 快跑
sudo ubusd & 起 ubus 总机(④ 需要) 早已在课时 31 做过
less 验收清单.md / less 答辩提纲.md 自评 / 演练 边看边填
exit $FAIL 式的脚本骨架 自己写检查脚本 见 1.1 的 ok/bad
编号 任务 提示 预期结果
L33.1 跑通 acceptance.sh 第 1 步 27 项全通过
L33.2 故意制造一处失败 改 offline_ms 为 60000 再跑 ⑦ 报失败,学会"看哪项红了"
L33.3 给脚本加一个新检查点 例如检查 /etc/config/device 里的 report_ms 与网关启动行一致 脚本变 28 项
L33.4 写自己的 README 别人照着能跑通(含依赖、步骤、现象) 同桌按它跑一遍不出错
L33.5 按清单自评 逐项填"自测"列 分数 ≥ 85
L33.6 答辩演练 掐表 5 分钟 + 随机抽 3 问 讲解流利、问题能答

三、作业(模块七项目交付物)

必交(按此清单打包,命名 学号-姓名-模块七项目/):

# 交付物 要求
1 代码 课时 24~32 你写/改过的文件;gcc -Wall -Wextra 零警告
2 README.md 依赖清单、编译命令、运行步骤(几个终端分别跑什么)、预期现象、常见问题
3 协议契约.md 帧格式、CRC、类型表(可沿用课时 24 的版本,改动要更新)
4 验收截图/记录 acceptance.sh 的完整输出(27 项)+ 网页界面截图 + 掉线/自愈各一张
5 《验收清单》自评表 逐项打分并写一句依据
6 答辩提纲 按模板填自己的架构图与两个"坑"

思考题(写在 README 末尾):

  1. 验收脚本里哪几项是"功能"、哪几项是"健壮性"、哪几项是"工程化"?为什么验收要三者都覆盖?
  2. 如果要把这个项目搬到真实硬件(设备换成单片机 + 真串口,网关跑在 ARM 开发板上),你需要改哪些地方?(提示:串口路径、交叉编译、systemd、网络)
  3. 这个项目里哪一处最脆弱?如果要再加一道保险,你会加什么?

四、模块七小结(把整条链路再走一遍)

text 复制代码
 ①设备面板(PyQt5)          ②网关(C, uloop)                 ③上位机(Flask 网页)
 ┌───────────────┐ 串口   ┌──────────────────────┐ TCP命令 ┌──────────────┐
 │ 灯/风扇/温度   │ 55AA帧 │ 四道防线:帧头→LEN→CRC │◀────────│ 按钮(表单)    │
 │ 0x80 每秒上报  │ ─────▶ │ →类型                  │         │              │
 │ 收命令改状态   │ ◀───── │ 状态表(灯/风扇/温度/在线)│ UDP上报 │ 页面显示状态  │
 └───────────────┘        │ 0x81 每 2 秒上报        │ ──────▶ │ /api/state   │
                          │ 配置读 uci、状态走 ubus  │  0x81   └──────────────┘
                          └──────────────────────┘
                             ↑ systemd 守护(挂了自动起)
  • 协议 :55 AA | TYPE | LEN | PAYLOAD | CRC16(低字节在前);TCP 外加 2 字节大端长度前缀
  • 职责划分:设备只管"状态 + 执行命令";网关管"转帧 + 记状态 + 判在线 + 对外提供接口";上位机管"显示 + 控制"
  • 工程化三件套:配置外置(uci)、状态可查(ubus)、崩溃自愈(systemd)
  • 可靠性三件套:帧同步 + CRC 校验(坏数据不进门)、缓冲区上限(不被灌爆)、在线判定(失联能发现)

全课程回头看:模块一~六(Ubuntu 基础、C 语言与结构体、进程与服务、网络编程、uloop 事件驱动、uci/ubus/JSON),到这里都变成了一个能演示的项目里的一颗螺丝钉------这就是"嵌入式 Linux 应用开发"的完整套路。

继续往哪走(选修/毕设方向):① 换成真实传感器(DHT11/DS18B20)与真串口;② 用 SQLite 存历史数据 + 画曲线;③ 用 MQTT 把状态发给云平台;④ 交叉编译到 ARM 开发板(课时 16 起的库要在板子上重新编);⑤ 给网页加登录与权限。


附录:怎么运行 + 脚本关键代码

三个文件在 代码/课时33-项目验收/:acceptance.sh(一键验收,十项 27 检查点)、验收清单.md(100 分自评表)、答辩提纲.md(5 分钟讲解 + 12 问)。

附录 A|完整跑一遍(含前置依赖)

shell 复制代码
# ===== ⓿ 前置:课时 24~32 的环境与程序都就位 =====
sudo apt install -y socat python3-pyqt5 python3-serial python3-flask xvfb
ls /usr/local/lib/libuci.so /usr/local/lib/libubus.so       # 课时 15 编的
ls ~/lab/课时31-网关产品化uci与ubus/gateway_pro               # 课时 31 编的
uci show device | head -3                                    # 课时 31 装的配置

# ===== ① 起 ubus 总机(验收第 ④ 项要用)=====
sudo ubusd &

# ===== ② 一键验收 =====
cd ~/lab/课时33-项目验收
sudo ./acceptance.sh            # 约 1~2 分钟;--quick 可跳过完整故障注入

# ===== ③ 看结果 =====
# 通过:27 项    失败:0 项
# 结论:模块七 综合项目 验收通过 ✓

# ===== ④ 自评 + 答辩演练 =====
less 验收清单.md
less 答辩提纲.md

acceptance.sh 开头的 C26/C29/C30/C31/C32 按脚本自身位置算 (脚本在 代码/课时33-项目验收/,往上两层就是 代码/),所以整仓库 clone / 解压到任何目录都能直接跑,不用改路径。另外,仓库里不带 C 编译产物:首次运行脚本会自动用 gcc 编译课时 31 的 gateway_pro(编过了就跳过),所以第 ⓿ 步不手工编译也能跑。

附录 B|acceptance.sh 的关键写法

B-1 计数与打印(整个脚本的骨架)

bash 复制代码
PASS=0
FAIL=0
ok()  { echo "  [通过] $1"; PASS=$((PASS + 1)); }
bad() { echo "  [失败] $1"; FAIL=$((FAIL + 1)); }

# 单项检查:条件成立就"通过",否则"失败"
[ -f /usr/local/include/libubus.h ] && ok "libubus 头文件在位" || bad "缺 libubus(回课时 15)"

B-2 把"现象"变成"可判断的条件"

bash 复制代码
# 环境是否齐全
command -v socat > /dev/null && ok "socat 已安装" || bad "socat 未安装"

# 设备帧是否真的在发(读面板日志)
grep -aq '发送 0x80' /tmp/acc_panel.log && ok "设备面板在发 0x80 状态帧" || bad "设备面板没有发帧"

# 状态表是否正确(从 ubus 的 JSON 里抠字段)
STATUS="$(ubus call device status 2>/dev/null)"
echo "$STATUS" | grep -q '"online": 1' && ok "online = 1(设备在线)" || bad "online 不是 1"
RX="$(echo "$STATUS" | grep -o '"ser_rx": [0-9]*' | grep -o '[0-9]*')"
[ "${RX:-0}" -gt 0 ] && ok "ser_rx = $RX(串口确实收到过帧)" || bad "ser_rx = 0"

B-3 主动"做动作"再检查(掉线、自愈)

bash 复制代码
# 掉线:杀掉设备 → 等 6 秒 → 看 online 是否变 0
pkill -f 'device_panel_seria[l]'
sleep 6
OFF="$(wget -qO- http://127.0.0.1:5000/api/state | grep -o '"online":[0-9]*' | grep -o '[0-9]*')"
[ "${OFF:-1}" = "0" ] && ok "设备掉线后 online 变成 0" || bad "掉线没判出来(online=$OFF)"

# 自愈:kill -9 → 等 5 秒 → 服务还在 active 且 NRestarts ≥ 1
PID1="$(systemctl show -p MainPID --value device-gateway)"
kill -9 "$PID1"
sleep 5
[ "$(systemctl is-active device-gateway)" = "active" ] && ok "kill -9 后服务被自动拉起" || bad "自愈失败"
NR="$(systemctl show -p NRestarts --value device-gateway)"
[ "${NR:-0}" -ge 1 ] && ok "重启次数 NRestarts = $NR" || bad "NRestarts = $NR"

B-4 收尾一定要清理(trap 保证异常退出也清理)

bash 复制代码
cleanup_all() {
    [ -n "${APP:-}" ] && kill $APP 2>/dev/null
    [ -n "${LISTEN:-}" ] && kill $LISTEN 2>/dev/null
    [ -n "${GW:-}" ] && kill $GW 2>/dev/null
    pkill -f 'device_panel_seria[l]' 2>/dev/null
    "$C32/install_service.sh" --uninstall > /dev/null 2>&1
    pkill -x socat 2>/dev/null
    sleep 1
}
trap cleanup_all EXIT

导出结论 :exit 0(全通过)/ exit 1(有失败)------ 这样以后放进 CI(比如 gitee 的流水线)也能用。

相关推荐
ggaofeng1 小时前
Ctrl+C结束进程是如何实现的
服务器·c语言·网络
和裕1 小时前
年度框架直供 vs 零散按需采购:定制纸箱采购成本、交付与服务核心区别全对比
大数据·运维·网络·人工智能·算法
Galeoto2 小时前
如何判断路由器的网络情况
网络
尹人入圣3 小时前
市面上IP驱动产业新场景新工具
运维·网络·python·tcp/ip
小马同学-3 小时前
OpenStack网络管理-neutron
网络·openstack·neutron
贾斯汀frank4 小时前
BinSentry 自研二进制哨兵压力测试+冒烟回归脚本
网络
最强小杰5 小时前
claude-opus-5.5 API 接入教程:anthropic-version 头、tools 定义、流式事件格式三个常见问题全部梳理,收藏备用
java·服务器·网络·ai
网络小江5 小时前
上网第三十二课:NAT——让 IPv4 地址“够用“的魔法
网络协议
宵时待雨5 小时前
linux笔记归纳25:多路转接epoll
linux·服务器·网络·c++