188 lines
16 KiB
Markdown
188 lines
16 KiB
Markdown
# 奇妙小屏幕控制器(核桃派 ZeroW)
|
||
|
||
本目录是核桃派 ZeroW/H618 的独立服务源码,控制一块 64×64、1/32 扫描、ABCDE 行寻址的 HUB75 RGB 点阵屏。生产驱动是项目自带的 `walnutpi-h618-hub75`,不依赖其他开发板的 GPIO 库。
|
||
|
||
## 路径与运行方式
|
||
|
||
移动端控制器独立维护,入口见 `../移动端相关内容/README.md`;设备侧新增契约见 `../整体开发需求/03_移动端接入需求.md`。设备更新只评估并登记手机兼容影响,不自动迭代 App。BLE 与网页应复用公共业务层;不得让 App 依赖网页布局。当前蓝牙接入尚待实现,既有脚本仍执行原禁用策略,不能将需求文档当成设备已启用蓝牙。
|
||
|
||
普通前端/BLE 入口改动不新增实屏视觉验收。确需视觉检查时先停止、解释原因、询问用户如何启动并等待指示,不自动开摄像头、DroidCam 或视觉程序,不主动切换实屏测试图案。软件统计和逻辑帧检查按正常自动测试执行。
|
||
|
||
下列源码运行、编译和手工部署命令均在本文件所在的 `核桃派软件源代码/` 目录执行;发布命令另行标明从工作区根目录执行。Windows 本机测试使用根目录下 `测试相关资料/如何测试/本机测试环境/README.md` 的命令。
|
||
|
||
- 程序:`/opt/matrix-screen-controller`
|
||
- 持久数据:`/var/lib/matrix-screen-controller`
|
||
- 运行数据:`/run/matrix-screen-controller`
|
||
- 服务:`matrix-screen-controller.service`
|
||
- 网页:`http://<设备通过 DHCP 获得的 IPv4>:8080/`
|
||
|
||
本机 mock:
|
||
|
||
```bash
|
||
python3 -m venv .venv
|
||
.venv/bin/pip install -r requirements.txt
|
||
MATRIX_DRIVER=mock .venv/bin/python -m uvicorn app.main:app --host 127.0.0.1 --port 8080
|
||
```
|
||
|
||
核桃派不具备境外网络条件。生产安装必须使用项目根目录 `发布更新相关/其他依赖/aarch64-py311/` 中已校验的 wheel,并强制 `--no-index`:
|
||
|
||
```bash
|
||
python3 -m venv .venv
|
||
.venv/bin/pip install --no-index --find-links ../发布更新相关/其他依赖/aarch64-py311 -r requirements.txt
|
||
make -C app/display/native clean all
|
||
```
|
||
|
||
## 原生驱动边界
|
||
|
||
- PIO 控制器:`allwinner,sun50i-h616-pinctrl`
|
||
- 映射基址:`0x0300B000`
|
||
- PWM 控制器:`0x0300A000` 的 PWM4,OE 使用 `PI14/PWM4`,LAT 使用 `PI15/GPIO`
|
||
- 硬件映射:`walnutpi-pi-bank-pwm-oe-v2`,不得搭配旧版 LAT/OE 接线
|
||
- PI bank:偏移 `0x120`,数据寄存器再偏移 `0x10`
|
||
- 图像:64×64 RGB888 输入、7-bit PWM、双缓冲帧边界交换
|
||
- 刷新档:15/20/30/45/60/80/100 Hz
|
||
- CPU3 绑定、实时优先级、内存锁定和 governor 会逐项报告实际结果,状态不得伪报成功
|
||
- 专用主机停用 lightdm、蓝牙、官方 `gpioc-server` 和 `map_device`;Wi-Fi、SSH、NetworkManager 不受影响。正式可刷镜像默认启用 SSH,使用配置账户密码登录并以同一密码执行完整 sudo,禁止 root 直接登录
|
||
- 退出或异常时先禁用 PWM4 并强制 OE 高、清屏,再恢复 GPIO/PWM 状态;systemd 前后都执行独立 `hub75_safeoff`
|
||
|
||
编译和纯逻辑测试:
|
||
|
||
```bash
|
||
make -C app/display/native clean all test
|
||
```
|
||
|
||
真实 `/dev/mem` 基准必须在 HUB75 和 ADC 都断开时、由 root 执行:
|
||
|
||
```bash
|
||
sudo app/display/native/hub75_benchmark --duration 60 --refresh-rate 100 --brightness 40 --dev-mem
|
||
```
|
||
|
||
接屏门槛与全部人工停顿顺序见项目上层 `测试相关资料/如何测试/核桃派点阵屏控制服务测试流程.md`。
|
||
|
||
## 闪烁修复状态
|
||
|
||
- 旧版 CPU 忙等 OE 已复现局部高亮块;当前版本使用 H618 PWM4 的硬件单脉冲模式(`PWM_MODE/PWM_PUL_START`)自动结束低有效 OE 脉冲,并在点亮当前锁存画面的同时移入下一 bitplane,以 50% 扫描槽预算恢复 `1..100%` 全范围亮度。不得用 `PER` 反复启停连续 PWM 模拟单脉冲。该实现尚须依次通过板端、实屏与摄像头验收,在这些门槛完成前不得写成“闪烁已解决”。
|
||
- 任一门槛失败必须回滚旧 `/opt`、保持持久数据不变,并在断电后恢复原 LAT/OE 接线;不得把部分亮度通过写成全部通过。
|
||
|
||
## I²C 与电压采样
|
||
|
||
Unit ADC 仅使用物理脚 3/5 的 `/dev/i2c-1`(PI8/PI7),默认地址 `0x48`。HUB75 驱动不会更改 PI7、PI8 和保留的 PI16。ADC 必须等屏幕基础画面全部通过后,在整机断电状态下连接;校准时万用表和 ADC 必须测量同一点。
|
||
|
||
## 数据安全
|
||
|
||
配置 schema 为 v8,包含设备共享的 `workspace_order`,并在 v7→v8 时把 `media-import` 插入文字与动图工作区之间。媒体源文件和严格 schema v1 任务记录位于 `/var/lib/matrix-screen-controller/media-import/jobs`;成功或取消立即清理,失败及等待设置保留 24 小时。只有数据文件不存在时才能创建默认值;损坏、未知更高版本或迁移失败时保留原文件并拒绝启动。更新程序必须先克隆 `/var/lib/matrix-screen-controller`,在副本上完成登记迁移并验证后再原子切换,不得把设备凭据、主机密钥、旧设备配置或测试数据打进部署载荷。
|
||
|
||
媒体转换依赖 Debian 的 `ffmpeg`、`ffprobe`、`heif-convert`(`libheif-examples`)以及带 `zscale`、`tonemap` 的 FFmpeg 构建。分析和转换由 `matrix-screen-converter@.service` 在 CPU0–1、150% CPU、384 MiB 内存、无交换区和私有网络限制下串行执行;主服务只负责流式上传、任务管理与结果展示。
|
||
|
||
现有设备使用原子更新脚本;它在版本化 staging 中离线安装、编译和测试,健康检查失败会自动恢复旧 `/opt`:
|
||
|
||
```bash
|
||
sudo sh scripts/update_walnutpi.sh
|
||
```
|
||
|
||
## 浏览器全量 OTA
|
||
|
||
OTA 会记录更新前的候选内核、cpufreq 和性能模式状态。原本正常的设备若在更新中失去这些能力,应用更新按事务回滚;原本已回退到原内核的设备允许先更新应用。更新完成后,若已安装的候选 Image、两份 DTB、模块树、受管启动脚本和健康服务均与登记材料一致,独立任务仅安排一次候选重启;结果保存在持久根的 `kernel-recovery.json`,通过 `/api/ota/status.kernel_recovery` 查询。失败后不会在后续 OTA 自动重试。缺失或损坏候选材料时只提示需要专用内核修复包,普通 OTA 不携带大型内核归档。
|
||
|
||
系统设置显示 `VERSION` 中的正式软件版本和 `FEATURE_UPDATED_AT` 中固定的北京时间,并允许上传完整 `.ota` 文件。页面不展示最近 OTA 结果,但上传、安装、断线恢复和失败反馈保持可见;失败后会自动打开可滚动、可复制的完整诊断日志,关闭后仍可重新查看或复制。设备只在 `/var/lib/matrix-screen-controller/ota/last-failure.log` 原子保留最近一份不超过 1 MiB 的脱敏失败日志,下一次成功更新会将其删除;`GET /api/ota/failure-log` 仅在最近结果失败且日志存在时返回 `text/plain`,并禁止缓存。设备不连接更新服务器,不使用增量、加密或签名;包内 SHA-256 只检查文件损坏。同版和旧版在停服前拒绝,新版失败自动恢复更新前程序与用户数据。
|
||
|
||
从 1.0.3 首次升级到包含该能力的版本时,安装过程仍由 1.0.3 的旧 worker 控制,因此新日志页面只能在升级成功后使用。新版本测试入口兼容旧 worker 传入的隔离变量,并将 pytest、应用数据、运行数据和 Python 临时文件全部约束到 OTA 事务目录;它不会为安装日志功能而忽略测试失败或绕过原子回滚。
|
||
|
||
普通功能开发、修复和 SSH 开发部署都保持 `VERSION` 不变;每次可部署的功能新增或优化把 `FEATURE_UPDATED_AT` 写为精确到分钟的固定 `+08:00` 时间。只有用户明确要求导出 OTA 或可刷镜像时才推进共用正式版本;单纯导出、重复部署和文档修改不得刷新功能时间。普通更新成功导出默认将补丁位加一;新增或变更系统软件必须增加 minor 并归零 patch,使用 `--version` 指定已登记节点。失败不占号,全部操作登记在项目根 `发布记录.json`。
|
||
|
||
需要交付或测试 OTA 包时才从项目根执行:
|
||
|
||
```powershell
|
||
python ".\核桃派软件源代码\scripts\export_release.py" ota --notes "本次更新说明"
|
||
```
|
||
|
||
OTA 输出为 `发布更新相关/OTA数据包/<版本>/`;镜像输出为 `发布更新相关/导出包/<版本>/`。已有版本目录不会覆盖。导出 IMG 可由用户删除以节省空间,缺失默认正常,不追补、不自动重建、不影响开发或常规测试。OTA 今后长期保留;1.0.1 缺失为已接受的历史例外,不再追补或提醒,不影响使用。后续构建不得引用历史产物,原发布记录保留。
|
||
|
||
## 可刷镜像导出与同版本修复
|
||
|
||
镜像导出只在 Linux root 构建主机上执行。入口使用标准库完成打包和镜像操作,不要求主机安装 Pillow、FontTools 或应用 venv;应用依赖由镜像中的 AArch64 离线 wheelhouse 在首次启动时安装。执行前先检查构建主机,而不是假设远端具备本机工具:
|
||
|
||
```bash
|
||
command -v python3
|
||
command -v bash
|
||
command -v losetup
|
||
command -v mount
|
||
command -v umount
|
||
command -v systemctl
|
||
command -v sha256sum
|
||
```
|
||
|
||
普通新版本镜像需要显式配置 JSON,并使用两阶段门禁。第一阶段只生成静态验证完成的候选,不修改正式版本或发布记录:
|
||
|
||
```bash
|
||
sudo python3 核桃派软件源代码/scripts/export_release.py image \
|
||
--version 1.1.1 --config /受保护临时目录/image-config.json \
|
||
--candidate-output /受保护临时目录/image-1.1.1-candidate \
|
||
--notes "完整可刷镜像 1.1.1"
|
||
```
|
||
|
||
同一字节候选完成真实 TF 卡首启、默认 Wi-Fi、SSH、sudo、内核、网页和再次重启验收后,使用不含秘密且与 IMG 摘要绑定的严格 JSON 晋升;晋升会重建期望 bundle 并重新只读验证镜像,成功后才推进版本:
|
||
|
||
```bash
|
||
sudo python3 核桃派软件源代码/scripts/export_release.py image \
|
||
--validated-candidate /受保护临时目录/image-1.1.1-candidate \
|
||
--validation-report /受保护临时目录/image-1.1.1-validation.json \
|
||
--notes "完整可刷镜像 1.1.1"
|
||
```
|
||
|
||
IMG 自带完整系统依赖,不应用 OTA 必经节点跳转规则。正式 IMG README 是公开默认账户和 Wi-Fi 值的唯一发布文档;建议用户刷写前用镜像编辑器修改。已经登记但经真实首启证明不可用的当前版本,只有获得明确授权后才使用同版本修复入口:
|
||
|
||
```bash
|
||
sudo python3 核桃派软件源代码/scripts/export_release.py image \
|
||
--repair-current \
|
||
--notes "同版本镜像修复说明"
|
||
```
|
||
|
||
修复入口从现有正式 IMG 复用完整双槽配置,从未修改官方基线重建,在隐藏目录完成全部验证后才替换正式目录和原发布记录;不追加重复版本、不修改 `VERSION`。失败会恢复旧目录和记录。旧的 `refresh_sd_image_payload.py` 原地刷新方式已停用,因为它不能安全处理 rootfs 应用载荷和 FAT 内核安装空间。
|
||
|
||
镜像格式 v3 在 FAT16 中只放配置、元数据和首启入口;应用 bundle 与候选内核分别预置到 `/opt/matrix-image-bootstrap/app` 和 `/opt/matrix-image-bootstrap/axp313a`。构建及设备安装均要求候选 boot 文件实际预算之外仍有 `32 MiB` 余量。首次启动失败保留 rootfs 压缩载荷供重试,全部成功后才统一删除。
|
||
|
||
## 测试
|
||
|
||
```bash
|
||
python -m pytest -q
|
||
node --test tests/*.mjs
|
||
```
|
||
|
||
真实硬件测试必须严格按上层测试文档逐项执行,上一项失败时不得跨过。任何接线、拔插、万用表测量或电压调整前都必须暂停并等待人工确认。
|
||
|
||
## 软件安装节点与 1.1.0
|
||
|
||
从 1.1.0 起,每个 minor 的 `.0` 是**软件安装包(必经升级版本)**。例如 `1.0.6 → 1.1.0 → 1.2.0 → 1.2.3`;不能跳过任何安装节点。同系列补丁可以跳过,例如 `1.1.0 → 1.1.3`。已预装 frp 的 1.0.6 调试设备也必须先完成 1.1.0,版本号与组件完整性分别检查。同版和降级仍不允许。
|
||
|
||
新 OTA 先导出到正式目录外的候选目录,完成真实设备试装及原版本恢复后,再按候选包 SHA-256 原字节晋升。以下命令从项目根执行;`<受保护临时目录>` 不得位于正式 OTA 发布目录内:
|
||
|
||
```powershell
|
||
python "核桃派软件源代码/scripts/export_release.py" ota --candidate-output "<受保护临时目录>/ota-候选" --notes "更新说明"
|
||
python "核桃派软件源代码/scripts/export_release.py" ota --validated-candidate "<受保护临时目录>/ota-候选" --validation-report "<受保护临时目录>/实机验收.json" --notes "更新说明"
|
||
```
|
||
|
||
候选阶段不改 `VERSION` 或发布记录;正式目录包含候选原字节包、摘要、manifest 和 README。报告只含包摘要、目标/试装源/恢复版本、时间及离线安装、OTA 健康、运行目录、用户数据、FRP、蓝牙、事务清理、恢复原版本的布尔结果,不写凭据或设备标识。每个 minor 的 `.0` 仍需用 `--version` 明确指定登记的必经安装节点。直接打包脚本只用于非正式材料。
|
||
|
||
`UPGRADE_POLICY.json` 登记连续安装节点及固定依赖摘要;已发布节点不得改写。1.1.0 使用旧更新器支持的 v1 外层协议,frpc 放在 `software/system-dependencies/frpc/`。以后使用 v2,正式旧更新器会拒绝该格式;新更新器会在安装前提示必须先安装的版本。普通补丁不携带系统二进制,仍包含应用及 Python wheelhouse;系统组件缺失时先修复再更新。首装镜像仍携带完整离线依赖。
|
||
|
||
frpc 完整匹配时不替换二进制或重启服务;否则离线修复,保留 UUID 配置、选择项和启停状态。初次默认关闭。OTA 从现有 frp 非 root 账户、镜像生成的 `/etc/sudoers.d/90-matrix-screen-controller-account` 或唯一 sudo 普通账户确定运行账户,无法确定则在修改前失败。
|
||
|
||
`prepare_data_root` 仅在根权限、真实 OTA 请求、候选源码和候选数据路径全部匹配时启动组件事务,普通迁移和测试不会安装系统软件。完整 pytest 必须先通过。事务记录及原组件备份镜像保存在原数据根和候选数据根的 `ota/component-transaction/`;系统启动恢复程序临时位于 `/opt/matrix-screen-controller-component-recovery.py`,恢复 unit 为 `matrix-screen-component-recovery.service`,运行期监护为 `matrix-screen-component-watch.service`。原 worker 成功记录与目标版本共同决定提交;失败或中断恢复原组件、权限、程序和数据。无法完成恢复时保留记录并拒绝下一次更新;成功恢复/提交后删除事务文件、临时恢复程序和 unit,成功提交还删除程序目录内的安装载荷。只包含运行状态的文件仍位于 `/run/matrix-screen-controller`。
|
||
|
||
## OTA 停服运行目录保护(必须保留的防回归规则)
|
||
|
||
`RuntimeDirectoryPreserve=restart` 只保护重启,不保护分开的 `systemctl stop` / `start`。OTA 会显式停止主服务,因此主服务使用 `RuntimeDirectoryPreserve=yes`;该运行目录仍位于 `/run`,整机重启后清空。日志写入重新 mkdir 只能救回日志,不能救回已经删除的请求和进度,不能作为生命周期修复。
|
||
|
||
从旧版本安装时,候选迁移入口必须先创建 `/run/systemd/system/matrix-screen-controller.service.d/90-matrix-ota-runtime.conf`,执行 daemon-reload 并检查有效 `RuntimeDirectoryPreserve=yes`,再允许旧 worker 停服。保护一直保留到提交或回滚及清理结束;原有用户 drop-in 不覆盖。该标准 systemd 运行期目录是临时服务配置的明确例外;普通运行数据继续集中在 `/run/matrix-screen-controller`。
|
||
|
||
真实验收使用 `sudo python3 scripts/test_runtime_directory_systemd.py`,仅操作随机命名的专用测试 unit/目录,不停止生产服务。必须包含旧配置失败对照、显式 stop、restart、启动失败及保护清理,不能用 mock systemctl 代替。诊断存储故障不得中断回滚,有限内存缓冲可补写最近失败日志。
|
||
|
||
经用户明确授权修复当前 OTA 时,先构建到隔离候选目录并实机验收,再执行:
|
||
|
||
```powershell
|
||
python "核桃派软件源代码/scripts/export_release.py" ota --repair-current --validated-candidate "<已验收候选包>" --validation-report "<实机验收JSON>" --notes "修复说明"
|
||
```
|
||
|
||
此入口校验原产物及记录、候选摘要、最终源码一致性和实机验收结果;完整归档原包与记录后,按原字节晋升候选包,不再构建、不推进 VERSION、不刷新功能时间。验证或提交失败保留原产物与记录。
|