24 嵌入式操作系统 | 项目启动与环境准备(PyQt5 / Flask 两个“最小可跑“)

嵌入式操作系统 | 项目启动与环境准备(PyQt5 / Flask 两个"最小可跑")

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

课件、示例代码与验收脚本都在该仓库,可直接 git clone 或下载 ZIP 使用。
模块七 · 2 学时 · 环境:VMware + Ubuntu 20.04

一句话概述 :先用 20 分钟把项目讲清楚(三个程序、三层架构、一套帧协议),然后动手把环境跑起来 :一条 apt 命令装好 PyQt5/pyserial/Flask,跑出一个 Qt 窗口和一个网页,把虚拟串口准备好,把《协议契约》填好------这节课只要"能跑",不写业务代码。

本节目标

  1. 能画出项目三层架构,说清三个程序各自干什么、之间怎么传数据
  2. 会用一条 apt 命令 装好 Python 三件套,并用 check_env.py 自检通过
  3. 能跑出最小 PyQt5 窗口 和最小 Flask 页面 ,并知道它们分别在哪里看(这是本课重点)
  4. 会复习课时 22 的三条命令把虚拟串口准备好,并把《协议契约》填好(组内人人一份)

一、讲解内容

1.1 知识讲解

我们要做的项目。 一句话:用界面控制一个"模拟设备",而设备是用串口连着的。

复制代码
┌──────────────────────┐   TCP:控制命令(同一套帧)        ┌────────────────────────────┐
│ 上位机界面            │ ─────────────────────────────▶    │ C 网关(uloop 事件循环)      │
│ ① Flask 网页(浏览器) │ ◀─────────────────────────────    │ · 串口侧:发命令 / 收状态     │
│ ② 或 PyQt5 窗口        │   UDP:状态上报(每 2 秒)          │ · TCP 命令通道(uloop_fd)    │
└──────────────────────┘                                   │ · ubus:device.status        │
                                                           │ · uci:串口/端口/间隔         │
                                                           └──────────────┬─────────────┘
                                                                            │ socat 虚拟串口
                                                                            │ 55 AA 帧 + CRC16
                                                           ┌──────────────▼─────────────┐
                                                           │ 设备模拟器(PyQt5 面板)      │
                                                           │ LED 灯 / 风扇档位 / 温度      │
                                                           └────────────────────────────┘

三个程序(正好三个人一台戏):

程序 用什么写 干什么 跑在哪
设备模拟器 Python + PyQt5 显示并执行"设备"的动作:灯亮灭、风扇档位、温度 虚拟机桌面
C 网关 C + uloop 中间人:串口 ⇄ 网络互相转发;维护状态;uci 读参数、ubus 暴露状态 终端
上位机界面 Python + Flask(网页)或 PyQt5(窗口) 看状态、点按钮下命令 浏览器 / 桌面

三条数据流(后面每节课都会用到,先记住名字):

  1. 控制下行:点按钮 → TCP 命令帧 → 网关 → 串口帧 → 设备动作(灯亮、风扇转)
  2. 状态上行:设备 → 串口状态帧 → 网关更新"最新值" → 每 2 秒 UDP 上报 → 界面刷新
  3. 旁路查询 :ubus call device status(课时 20)看状态;改 /etc/config/device(课时 19)改参数

❗ 本课最重要的一条提示:两种界面"在哪里看"完全不同

界面 能在哪看 不能在哪看
PyQt5 窗口 ① 虚拟机桌面 里开的终端(VMware/VirtualBox 窗口里)② 带 X11 转发的 SSH(ssh -X,Windows 上还要装 X 服务器,麻烦) ❌ 普通 VS Code Remote-SSH 终端 → 会报 could not connect to display
Flask 网页 ① 虚拟机里的浏览器 ② Windows 宿主机 浏览器:http://<虚拟机IP>:5000 ③ VS Code Remote-SSH:端口会自动转发,点一下就能在宿主机浏览器打开 几乎没有限制(这就是我们建议"上位机做成网页"的原因)

结论:做 PyQt5 的实验(课时 25、26)请到虚拟机自己的桌面里去敲 ;网页部分随便在哪都行。

如果确实没有桌面(比如只在 SSH 里),可以装 xvfb 用"虚拟显示器"自检:xvfb-run -a python3 xxx.py --shot /tmp/a.png------程序会渲染并存一张图给你看。

为什么这么分工(选型说明) :设备面板和上位机要"画面",用 Python 生态最省事;网关是长期运行、接串口和网络的核心,用 C + uloop(课时 15--23 学的那套)又省资源又能复习前面全部知识。真去工厂做项目也是这么分工的。

Python 三件套(都用 apt 装,因为虚拟机里没装 pip):

库 干什么 本课以后用在哪
python3-pyqt5 画窗口、控件、按钮 设备模拟器(课时 25/26)
python3-serial(pyserial) Python 读写串口 设备模拟器接串口(课时 26)
python3-flask 起一个网页服务 上位机界面(课时 30)

Qt 事件循环和 uloop 是一回事(这个类比能省很多口水):

C(uloop) Python(Qt)
uloop_init() app = QApplication(...)
uloop_fd_add() / uloop_timeout_set() btn.clicked.connect(槽) / QTimer
uloop_run() 进循环等事件 app.exec_() 进循环等事件
回调函数 槽函数(slot)

所以 GUI 程序里绝不能写 while True :那样事件循环就转不起来了,界面会白屏卡死------要定时干活就用 QTimer(课时 25 会用到)。

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

本节前两步只是"装东西和检查",不写代码 。学生什么都不会也没关系:一条一条照着敲,每条都有"应该看到什么"。哪条不对就先解决它,再往下走。

第 1 步:把环境装好(照着敲,别一口气全贴)

1-1 先刷新软件列表(第一次装东西一定要做;连不上网的先解决网络/换源)

shell 复制代码
sudo apt update

现象:最后一行出现 Reading state information... Done(中间有几个 Ign 或 Hit 都不用管)。

如果这里就报错(Could not resolve、Failed to fetch、Temporary failure resolving):说明虚拟机没网或源不可用------

先看 ping -c 2 www.aliyun.com 通不通;不通就检查 VMware 的"网络适配器"是不是选在 NAT(课时 01 讲过)。

换源的办法在课时 06:sudo sed -i 's|archive.ubuntu.com|mirrors.aliyun.com|' /etc/apt/sources.list && sudo apt update

1-2 装窗口界面库 PyQt5(课时 25、26 用)

shell 复制代码
sudo apt install -y python3-pyqt5

现象:会刷很多行,最后出现 Setting up python3-pyqt5 (...),然后回到提示符。中途若问 Do you want to continue? [Y/n] 直接回车。

(本机实测:这一步连同依赖要下载十几 MB、装完占磁盘几十 MB。)

1-3 装串口库 pyserial(课时 26 用)

shell 复制代码
sudo apt install -y python3-serial

现象:最后出现 Setting up python3-serial (...)。

1-4 装网页库 Flask(课时 30 用)

shell 复制代码
sudo apt install -y python3-flask

现象:最后出现 Setting up python3-flask (...)。

三件套合起来:下载 17.7 MB、占磁盘 75.3 MB (本机实测,df -h / 能看出来)。想省事也可以一条命令装完:

sudo apt install -y python3-pyqt5 python3-serial python3-flask------但分开装更好排查:哪条失败一眼就看到。

1-5(可选)装"虚拟显示器" xvfb:没有桌面的同学用它把 Qt 窗口渲染成图片

shell 复制代码
sudo apt install -y xvfb

现象:装完后 xvfb-run --help 能打出帮助(而不是 command not found)。

1-6 确认课时 15 编译的三个 C 库还在(课时 27 起网关要用 libubox/ubus/uci)

shell 复制代码
ls /usr/local/include/libubox/uloop.h
ldconfig -p | grep -E 'libubox|libubus|libuci'

现象(本机实测,三条 => /usr/local/lib/... 都要有):

text 复制代码
/usr/local/include/libubox/uloop.h
	libuci.so (libc6,x86-64) => /usr/local/lib/libuci.so
	libubus.so (libc6,x86-64) => /usr/local/lib/libubus.so
	libubox.so (libc6,x86-64) => /usr/local/lib/libubox.so

缺了怎么办:回课时 15 ,按 libubox → ubus → uci 的顺序编译安装一遍(源码包在 代码/依赖源码包/);

装完记得 sudo ldconfig,再回到这条命令确认。

1-7 确认其它要用的工具都在(一条命令全查)

shell 复制代码
which socat git gcc make cmake pkg-config
ls /usr/include/json-c/json.h

现象(本机实测;socat 是课时 22 起一直在用的"虚拟串口"工具):

text 复制代码
/usr/bin/socat
/usr/bin/git
/usr/bin/gcc
/usr/bin/make
/usr/bin/cmake
/usr/bin/pkg-config
/usr/include/json-c/json.h

缺哪个就装哪个(都是一条命令):

shell 复制代码
sudo apt install -y socat                # 虚拟串口
sudo apt install -y build-essential      # gcc / make
sudo apt install -y cmake pkg-config     # 课时 15 编译库要用的
sudo apt install -y libjson-c-dev        # 课时 21 JSON 用
sudo apt install -y git                  # 拉代码/交作业用

这一整套(1-1 到 1-7)就是本课程的环境安装清单,我在本节附录 F 里又整理了一份"从零开始照抄版",可以直接照着走一遍。

第 2 步:一条命令自检环境

shell 复制代码
cd ~/lab/课时24-项目启动与协议
python3 check_env.py

现象(实测):

text 复制代码
Python 版本:3.8.10
PyQt5(图形界面)   OK   5.14.1
pyserial(串口)     OK   3.4
Flask(网页界面)    OK   1.1.1

结论:环境就绪,可以开始做项目。

第 3 步:最小 Qt 窗口------必须在虚拟机桌面里跑

shell 复制代码
python3 hello_qt.py

现象:弹出一个 480×200 的窗口,标题是"课时24:我的第一个 Qt 窗口",中间两行绿色文字"图形环境 OK!/ 下一节课:用 Qt 搭一个设备面板"(下图是本机实测截图):

如果你在 VS Code 的 SSH 终端里跑(很常见),会看到这样的报错(本机实测):

text 复制代码
qt.qpa.xcb: could not connect to display
qt.qpa.plugin: Could not load the Qt platform plugin "xcb" in "" even though it was found.
This application failed to start because no Qt platform plugin could be initialized.

这不是代码错,是"这台机器没有屏幕可画"。两个办法:

办法 怎么做 适合谁
① 到虚拟机桌面里跑(推荐) 在 VMware 窗口里的 Ubuntu 桌面打开终端,cd 过去再 python3 hello_qt.py 所有人
② 用虚拟显示器自检 xvfb-run -a python3 hello_qt.py --shot /tmp/qt.png 只有 SSH 的同学

第 ② 种办法的实测输出(它不显示窗口,而是渲染成一张图片):

text 复制代码
窗口截图已保存:/tmp/qt.png(成功)

用图片查看器打开 /tmp/qt.png,看到的就是那个窗口的样子------能出图,说明代码和环境都没问题。

第 4 步:最小 Flask 页面------哪里都看得到

shell 复制代码
python3 hello_flask.py

现象(实测日志):

text 复制代码
 * Serving Flask app "hello_flask" (lazy loading)
 * Environment: production
   WARNING: This is a development server. Do not use it in a production deployment.
 * Debug mode: off
 * Running on http://0.0.0.0:5000/ (Press CTRL+C to quit)

这时三种看法(这就是"网页界面"的好处):

在哪里看 地址 说明
虚拟机里的浏览器 http://127.0.0.1:5000 最直接
Windows 宿主机浏览器 http://<虚拟机IP>:5000 本机实测虚拟机 IP 是 192.168.31.129,你的用 ip a 查;这说明界面不用挤在虚拟机里看
只用了 VS Code Remote-SSH VS Code 下方"端口 / Ports"面板 → 找到 5000 → 点"在浏览器中打开" VS Code 自动转发,最省事

不想开浏览器也能验证(SSH 里很方便):

shell 复制代码
wget -q -O - http://127.0.0.1:5000/ | head -6            # 抓页面
wget -q -O - http://127.0.0.1:5000/ | grep -o '第 <b>[0-9]*</b> 次刷新'   # 看计数器
ss -ltnp | grep 5000                                     # 谁在监听

现象(实测):

text 复制代码
  <h2>设备网关在线</h2>
  <p>这是 Flask 渲染出来的页面,第 <b>1</b> 次刷新。</p>
...
第 <b>2</b> 次刷新
LISTEN 0 128 0.0.0.0:5000 0.0.0.0:* users:(("python3",pid=16111,fd=3))

第 5 步:把虚拟串口准备好(复习课时 22 的三条命令)

shell 复制代码
socat -d -d pty,raw,echo=0,link=/tmp/ttyV0 pty,raw,echo=0,link=/tmp/ttyV1   # 终端 A,保持运行
ls -l /tmp/ttyV0 /tmp/ttyV1

现象:两个软链接出现(/tmp/ttyV0 -> /dev/pts/x、/tmp/ttyV1 -> /dev/pts/y)。

它们是后面几节课的"设备线":网关用 V0,设备面板用 V1。

第 6 步:填《协议契约》并用命令行核对 CRC(本课动手产出)

打开 协议契约.md,把"第三节 TYPE 表"和"第五节 扩展表"填好(组内人人一份)。然后用课时 22 的工具核对 CRC:

shell 复制代码
python3 ../课时22-串口命令行调试/crc_check.py --crc 01 01 01

现象(实测):

text 复制代码
输入字节:01 01 01
CRC = 0x50E0(低字节在前:E0 50)
完整帧:01 01 01 E0 50

加上帧头 55 AA,就是"点亮 LED "的完整帧:55 AA 01 01 01 E0 50。

其余四条命令的实测结果(直接抄进你的契约表,也用来检查自己算得对不对):

命令 完整帧
关灯 55 AA 01 01 00 21 90
风扇 3 档 55 AA 02 01 03 91 91
查状态 55 AA 03 00 01 40
复位 55 AA 04 00 03 70
状态上报(灯亮/风扇2档/25.0℃) 55 AA 80 04 01 02 00 FA CE 64

第 7 步:收工。 Flask 窗口按 Ctrl+C 停;终端 A 的 socat 也 Ctrl+C(虚拟串口随即消失:ls -l /tmp/ttyV0 会报"没有那个文件")。

1.3 常见错误与排查

现象 原因 解决
could not connect to display / no Qt platform plugin could be initialized 在没有屏幕的地方跑 GUI(SSH) 到虚拟机桌面里跑;或 xvfb-run -a python3 xxx.py --shot /tmp/a.png
xvfb-run: command not found 没装 xvfb sudo apt install -y xvfb
Flask 启动报 Address already in use 5000 端口被上一次没退干净的程序占着 `ss -ltnp
Windows 宿主机打不开 http://IP:5000 ① 程序只监听了 127.0.0.1 ② IP 拿错 ③ 虚拟机网络模式是"仅主机"且宿主机不同网段 确认代码里 app.run(host="0.0.0.0");ip a 查 IP;VMware 用 NAT/桥接,或在虚拟网络编辑器里加端口转发
sudo apt install 装不上 没网 / 源不可用 课时 06 讲过换源;也可用老师给的离线包
import PyQt5 报错,但 apt 说装了 用错了 Python(conda/venv 里的 python) 一律用系统 python3
界面里中文变成方块 虚拟机缺中文字体 sudo apt install -y fonts-wqy-zenhei(本机实测自带,正常显示)
端口/进程乱 忘记之前的程序还在跑 `ps -ef

二、学生练习

本节命令速查表:

命令 作用 示例
sudo apt install -y python3-pyqt5 python3-serial python3-flask 装 Python 三件套 见第 1 步
python3 check_env.py 一条命令自检环境 ---
python3 hello_qt.py 跑最小 Qt 窗口(要在桌面里) 见第 3 步
xvfb-run -a python3 hello_qt.py --shot /tmp/qt.png 没有桌面时渲染成图片自检 ---
python3 hello_flask.py 起最小网页服务(5000 端口) 见第 4 步
wget -q -O - http://127.0.0.1:5000/ 不开浏览器抓页面 ---
`ss -ltnp grep 5000` 看谁在监听端口
ip a 查虚拟机 IP(宿主机访问用) ---
socat -d -d pty,... 造一对虚拟串口(课时 22 的三条命令) 见第 5 步
python3 ../课时22-串口命令行调试/crc_check.py --crc 01 01 01 算协议帧的 CRC 见第 6 步
编号 任务 提示 预期结果
L24.1 装三件套并自检 第 1、2 步 check_env.py 三行全 OK
L24.2 在虚拟机桌面里跑出 Qt 窗口 第 3 步 窗口 + 两行绿字(截图)
L24.3 在 SSH 里故意跑一次 Qt,看报错;再用 xvfb 自检 第 3 步两个办法 看到 could not connect to display,然后 窗口截图已保存...(成功)
L24.4 三种方式看 Flask 页面 第 4 步 虚拟机浏览器 + Windows 宿主机浏览器各截一张
L24.5 不开浏览器验证 Flask wget + ss -ltnp 看到页面内容和"第 N 次刷新"
L24.6 改 Flask 端口为 8080 再跑 改 app.run(port=8080) 浏览器 http://IP:8080 能开,5000 不再监听
L24.7 算出 5 条命令的完整帧填进契约表 第 6 步 与课件表格里的实测值一致
L24.8 造好虚拟串口并确认 第 5 步 ls -l 两个软链接

三、作业

  • 必做:交 4 张截图(check_env.py 输出 / Qt 窗口 / 虚拟机里的 Flask 页面 / Windows 宿主机 上的 Flask 页面),并回答:① 为什么 PyQt5 的窗口在 SSH 里打不开,而 Flask 的页面在 Windows 上也能看?② 三个程序(设备模拟器、C 网关、上位机界面)各自负责什么?③ 如果把模拟设备换成真实硬件(比如带继电器的开发板),三层里哪一层要改、怎么改?④ 协议帧里为什么要加 CRC16?
  • 选做:① 把 hello_flask.py 的页面改成自己的名字 + 学号 + 一句"这是我的项目";② 给 Qt 窗口加一个"你好"按钮,点了在窗口里显示一行字(提示:先看课时 25 的第 4 步写法)
  • 提交:截图 + 文字回答,命名 课时24-作业截图.png

四、本节小结与下节预告

本节把项目"立起来"了:三层架构、三条数据流、一套帧协议(连带 CRC 测试向量都算好了),还亲手把环境跑通了------Qt 窗口要在桌面看(或用 xvfb 存图),Flask 页面在 Windows 宿主机也能看,这个对照以后每节课都要用到。

下节课开始写第一个程序:用 Qt 控件把"设备面板"搭出来 (灯、风扇档位、温度、日志四块),并学会"信号 → 槽":点按钮 → 灯变绿 + 日志写一行。这节课不接串口,人人能过。


附录:怎么运行 + 完整代码

三个文件都在 代码/课时24-项目启动与协议/ 里,行尾请用 LF。

附录 A|怎么运行

shell 复制代码
# ① 装三件套(一次就够)+ 可选装 xvfb(没有桌面的同学)
sudo apt install -y python3-pyqt5 python3-serial python3-flask
sudo apt install -y xvfb                 # 可选

# ② 自检
cd ~/lab/课时24-项目启动与协议
python3 check_env.py

# ③ Qt 窗口:在虚拟机桌面里跑(SSH 里会报 could not connect to display)
python3 hello_qt.py
xvfb-run -a python3 hello_qt.py --shot /tmp/qt.png     # 没桌面时的替代办法

# ④ Flask 页面:虚拟机里跑,浏览器/宿主机都能看(Ctrl+C 停)
python3 hello_flask.py
ip a | grep inet                        # 查虚拟机 IP,宿主机浏览器开 http://IP:5000
wget -q -O - http://127.0.0.1:5000/ | head -6

# ⑤ 虚拟串口(终端 A,保持运行)
socat -d -d pty,raw,echo=0,link=/tmp/ttyV0 pty,raw,echo=0,link=/tmp/ttyV1
ls -l /tmp/ttyV0 /tmp/ttyV1

# ⑥ 算协议帧的 CRC(核对《协议契约》里的测试向量)
python3 ../课时22-串口命令行调试/crc_check.py --crc 01 01 01

附录 B|文件 check_env.py(一条命令自检环境)

python 复制代码
#!/usr/bin/env python3
"""课时 24 ------ 一条命令检查 Python 环境是否装好

用法:
    python3 check_env.py

现象(都装好了):
    Python 版本:3.8.10
    PyQt5(图形界面)  OK   5.14.1
    pyserial(串口)   OK   3.4
    Flask(网页界面)  OK   1.1.1

    结论:环境就绪,可以开始做项目。

如果缺包,按提示在虚拟机里执行(需要联网):
    sudo apt install -y python3-pyqt5 python3-serial python3-flask
"""
import importlib
import sys

# (显示名(已按中文宽度补好空格), 模块名, 取版本的方式)
CHECKS = [
    ("PyQt5(图形界面)  ", "PyQt5.QtCore", lambda m: m.PYQT_VERSION_STR),
    ("pyserial(串口)    ", "serial", lambda m: m.__version__),
    ("Flask(网页界面)   ", "flask", lambda m: m.__version__),
]


def main():
    print("Python 版本:%s" % sys.version.split()[0])
    all_ok = True

    for label, mod_name, get_ver in CHECKS:
        try:
            mod = importlib.import_module(mod_name)
        except ImportError as exc:
            print("%s 未装(%s)" % (label, exc))
            all_ok = False
            continue
        try:
            ver = get_ver(mod)
        except AttributeError:
            ver = "版本未知"
        print("%s OK   %s" % (label, ver))

    print()
    if all_ok:
        print("结论:环境就绪,可以开始做项目。")
        return 0

    print("结论:有缺的包,请在虚拟机里执行(需要联网):")
    print("    sudo apt install -y python3-pyqt5 python3-serial python3-flask")
    return 1


if __name__ == "__main__":
    sys.exit(main())

附录 C|文件 hello_qt.py(最小 Qt 窗口)

python 复制代码
#!/usr/bin/env python3
"""课时 24 ------ 最小 PyQt5 窗口:先确认"图形界面这条路"能走通

【重要:PyQt5 窗口必须在有桌面的环境里看】
    能用:① VMware/VirtualBox 的 Ubuntu 桌面窗口里开的终端
          ② 带 X11 转发的 SSH(ssh -X,Windows 上要装 X 服务器,比较麻烦)
    不能用:普通的 VS Code Remote-SSH 终端(会报 could not connect to display)。
    → 老师提示:做 PyQt5 相关实验(课时 25、26)都到虚拟机自己的桌面里去敲。

用法(三选一):
    ① 有桌面:  python3 hello_qt.py                      # 弹出一个窗口
    ② 只有 SSH:xvfb-run -a python3 hello_qt.py --shot /tmp/qt.png
                (xvfb 是"虚拟显示器",让程序在没有屏幕的地方也能渲染,
                  渲染完存一张 png------本文件专门留了 --shot 这个口子)
    ③ 只有 SSH 又不想装任何东西:跳过本文件,直接做 hello_flask.py

现象:窗口标题"课时24:我的第一个 Qt 窗口",中间一行绿色文字。
"""
import sys

from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QApplication, QLabel, QMainWindow, QVBoxLayout, QWidget


class HelloWindow(QMainWindow):
    """Qt 程序的三件套:QApplication(事件循环)+ 窗口 + 控件(用布局摆)"""

    def __init__(self):
        super().__init__()
        self.setWindowTitle("课时24:我的第一个 Qt 窗口")
        self.resize(480, 200)

        label = QLabel("图形环境 OK!\n下一节课:用 Qt 搭一个设备面板")
        label.setStyleSheet("font-size: 20px; color: #0a7a3d;")

        central = QWidget()                 # 窗口中间那块"身体"
        layout = QVBoxLayout(central)       # 垂直布局:控件从上往下摆
        layout.addStretch()                 # 上面留白
        layout.addWidget(label, alignment=Qt.AlignCenter)
        layout.addStretch()                 # 下面留白
        self.setCentralWidget(central)


def main():
    app = QApplication(sys.argv)            # ① 创建"事件循环"(和 C 的 uloop_init 一个意思)
    win = HelloWindow()                     # ② 创建窗口
    win.show()                              # ③ 显示它

    if "--shot" in sys.argv:                # 没有显示器时的自检:渲染并存图
        path = sys.argv[sys.argv.index("--shot") + 1]
        QApplication.processEvents()        # 先把界面画出来
        ok = win.grab().save(path)
        print("窗口截图已保存:%s(%s)" % (path, "成功" if ok else "失败"))
        return 0

    return app.exec_()                      # ④ 进入事件循环,等鼠标键盘事件(对应 uloop_run())


if __name__ == "__main__":
    sys.exit(main())

附录 D|文件 hello_flask.py(最小网页)

python 复制代码
#!/usr/bin/env python3
"""课时 24 ------ 最小 Flask 页面:确认"浏览器界面这条路"能走通

【好消息:Flask 的页面在哪里看都行】
    · 虚拟机里:浏览器打开  http://127.0.0.1:5000
    · Windows 宿主机:浏览器打开  http://<虚拟机IP>:5000   (虚拟机里用 `ip a` 查 IP)
    · 只用了 VS Code Remote-SSH:VS Code 会自动转发端口,
      在"端口/Ports"面板点一下"在浏览器中打开"就行
    (对比:PyQt5 的窗口就只能在虚拟机桌面里看,见 hello_qt.py 的说明)

运行(在虚拟机里):
    python3 hello_flask.py
停止:Ctrl+C

现象:浏览器里出现一个页面:"设备网关在线" + 第 N 次刷新。
"""
from flask import Flask

app = Flask(__name__)
HITS = {"n": 0}          # 页面被刷新了几次(下一节课会换成"真实设备状态")


@app.route("/")
def index():
    HITS["n"] += 1
    return """<!doctype html>
<html lang="zh-CN">
<head><meta charset="utf-8"><title>课时24:最小网页</title></head>
<body style="font-family: sans-serif; margin: 40px;">
  <h2>设备网关在线</h2>
  <p>这是 Flask 渲染出来的页面,第 <b>%d</b> 次刷新。</p>
  <p>下一节课(课时 25)先在虚拟机桌面里做一个 Qt 设备面板,<br>
     再下一节(课时 26)让那个面板能收串口命令。</p>
</body>
</html>""" % HITS["n"]


if __name__ == "__main__":
    # host="0.0.0.0":这样 Windows 宿主机也能访问;5000 是 Flask 的默认端口
    app.run(host="0.0.0.0", port=5000, debug=False)

附录 E|文件 协议契约.md(组内人人一份)

完整内容在 代码/课时24-项目启动与协议/协议契约.md,本节要填的是:

  1. 第三节 TYPE 表 :5 个 TYPE(0x01 灯、0x02 风扇、0x03 查状态、0x04 复位、0x80 状态上报)
  2. 第五节扩展表:自己想加的功能(比如第二路灯、蜂鸣器),先定 TYPE 再写代码
  3. 第四节测试向量 :用 crc_check.py --crc 算出完整帧,抄进表格

记不住也没关系:把附录里这张"实测帧"表抄到自己的契约文档上就够用了------

55 AA 01 01 01 E0 50(点灯)、55 AA 01 01 00 21 90(关灯)、55 AA 02 01 03 91 91(风扇3档)、

55 AA 03 00 01 40(查状态)、55 AA 04 00 03 70(复位)、55 AA 80 04 01 02 00 FA CE 64(状态上报)

附录 F|环境安装清单(从零开始"照抄版")

用途:刚装好 Ubuntu 虚拟机 / 换了台机器 / 做到一半发现缺东西 ,就从这里照抄一遍。

做法:一条一条敲,敲一条看一条的结果(每条下面都写了"应该看到什么")。全部做完约 10~20 分钟(看网速)。

F-0 前提检查(两条命令)

shell 复制代码
whoami                    # 应该输出你的用户名(本课程都用普通用户 + sudo)
ping -c 2 www.aliyun.com  # 能通就说明有网(不通先修网络,别急着装东西)

F-1 刷新软件列表(每次要装东西前都可以先做)

shell 复制代码
sudo apt update

应看到:Reading state information... Done

F-2 装 C 侧基础工具 + 虚拟串口 + JSON 库 (一条命令;先 apt update)

shell 复制代码
sudo apt install -y build-essential cmake pkg-config git socat libjson-c-dev

应看到:若干 Setting up ...,最后回到提示符。检查:

shell 复制代码
which gcc make cmake pkg-config git socat

应看到:/usr/bin/gcc、/usr/bin/make、/usr/bin/cmake、/usr/bin/pkg-config、/usr/bin/git、/usr/bin/socat

F-3 装 Python 侧三件套(+ 可选的虚拟显示器)

shell 复制代码
sudo apt install -y python3-pyqt5 python3-serial python3-flask
sudo apt install -y xvfb          # 可选:没有桌面时用它把 Qt 窗口渲染成图

应看到:Setting up python3-pyqt5 / python3-serial / python3-flask。自检:

shell 复制代码
python3 -c "import PyQt5, serial, flask; print('Python 三件套 OK')"

应看到:Python 三件套 OK

F-4 编译安装 C 侧三个库(libubox → ubus → uci)

这三个库 Ubuntu 源里没有,必须自己编译(课时 15 已经完整讲过一遍,这里只给"照抄顺序"):

shell 复制代码
# ① 解压课程给的源码包(在 代码/依赖源码包/ 里)
cd ~/lab && mkdir -p build && cd build
tar xzf ~/lab/依赖源码包/libubox-2023-05-23-75a3b870.tar.gz
tar xzf ~/lab/依赖源码包/ubus-2023-06-05-f787c97b.tar.gz
tar xzf ~/lab/依赖源码包/uci-2023-08-10-5781664d.tar.gz

# ② 依次编译安装(顺序不能变:ubus 和 uci 都依赖 libubox)
cd libubox-2023-05-23-75a3b870 && cmake . && make && sudo make install && cd ..
cd ubus-2023-06-05-f787c97b   && cmake -DBUILD_LUA=OFF . && make && sudo make install && cd ..
cd uci-2023-08-10-5781664d    && cmake -DBUILD_LUA=OFF . && make && sudo make install && cd ..
sudo ldconfig

自检(三条 => /usr/local/lib/... 都要有):

shell 复制代码
ls /usr/local/include/libubox/uloop.h
ldconfig -p | grep -E 'libubox|libubus|libuci'

更详细的每一步(每条命令的现象、常见报错、-Wno-error 之类)见课时 15 ;源码包与版本校验见 代码/依赖源码包/VERSIONS.txt。

F-5 一键总自检(做完上面四项,用这一段确认)

shell 复制代码
# C 侧
gcc --version | head -1
ls /usr/local/include/libubox/uloop.h
ldconfig -p | grep -c -E 'libubox|libubus|libuci'     # 应该是 3
# Python 侧
python3 --version
python3 -c "import PyQt5, serial, flask; print('PyQt5/pyserial/Flask OK')"
# 虚拟串口工具
which socat
# 有没有可用的显示(桌面里应输出 :0 之类;SSH 里是空的)
echo $DISPLAY

F-6 装不上怎么办(常见四种)

现象 原因 处理
Could not resolve / Failed to fetch 虚拟机没网 / DNS 检查 VMware 网络适配器选 NAT;ping -c2 www.aliyun.com;不行就换源(课时 06)
Could not get lock /var/lib/dpkg/lock 有另一个 apt 正在跑(比如后台在更新) 等 1~2 分钟;或 `ps -ef
No space left on device 磁盘满 df -h / 看;清 ~/.cache、旧内核、sudo apt clean
装完还是 import PyQt5 失败 用错了 Python(conda/venv) 一律用系统 python3;which python3 应是 /usr/bin/python3
界面中文是方块 缺中文字体 sudo apt install -y fonts-wqy-zenhei

给老师的提示 :F-1~F-4 可以提前在一台"母机"上做好,再把虚拟机克隆/导出成模板 发给学生;

学生只需跑 F-5 自检------但仍然建议第一节课让学生自己敲一遍 F-1~F-3,这是课时 06(软件管理)的落地练习。

相关推荐
编码者卢布5 小时前
【App Service 】WebJobs 多实例实验:谁在运行,什么时候运行?
开发语言·python
kaixin_learn_qt_ing5 小时前
Qt抽屉窗体实现demo
开发语言·qt
quantdash_cc5 小时前
Python 获取实时行情后如何进行批量筛选?从全市场快照到策略候选池
开发语言·python·数据分析·量化交易·股票数据·quantdash
论文复现现场5 小时前
论文复现看到“RTX 3090 or higher”:显存、CUDA、batch size 与 OOM 怎么判断?
开发语言·pytorch·batch·cuda·rtx3090
智鸟科技GemeOpen开发者智能设备5 小时前
GemeOpen 智能音箱 GSSM0B - 播放控制(Java示例)
java·开发语言·智能音箱
m0_380743876 小时前
PHP 8.3 接口返回数据为空怎么排查
开发语言·php
谢亮_vipxieliang6 小时前
Java 21 新特性实战:Record、Sealed、模式匹配
java·开发语言
霸道流氓气质6 小时前
LLM 应用限流与熔断机制完全指南:从多层防护架构到Java生产级弹性实战
java·开发语言·架构
大侠归来6 小时前
C语言内存管理:从栈到堆的完整指南
c语言·开发语言·python
m0_380743876 小时前
PHP7.0字符串在Docker怎么用
开发语言·php