--- a/AGENTS.md +++ b/AGENTS.md @@ -1,6 +1,6 @@ # 奇妙小屏幕控制器(核桃派 ZeroW)协作指南 -本目录是可以单独拿走、独立开发和部署的核桃派项目。处理任务前先读本文件,再读 `整体开发需求/`、`如何测试/` 和与任务有关的硬件资料。本项目不得依赖外层移植目录、原树莓派工作区、旧设备配置或旧设备凭据。 +本目录是可以单独拿走、独立开发和部署的核桃派项目。处理任务前先读本文件,再读 `整体开发需求/`、`测试相关资料/如何测试/` 和与任务有关的硬件资料。本项目不得依赖外层移植目录、原树莓派工作区、旧设备配置或旧设备凭据。 ## 1. 固定硬件和边界 @@ -9,7 +9,7 @@ - 真实驱动:项目自带 `walnutpi-h618-hub75` C 驱动,通过 `/dev/mem` 访问 H618 PI bank;不得回退或依赖 `rpi-rgb-led-matrix`。 - ADC:M5Stack Unit ADC v1.1 / ADS1110,`/dev/i2c-1`,地址 `0x48`。 - 生产程序、持久数据和运行数据分别位于 `/opt/matrix-screen-controller`、`/var/lib/matrix-screen-controller`、`/run/matrix-screen-controller`。 -- 当前设备和开发机凭据只保存在 `核桃派的用户名和密码和ip/用户名密码ip.txt`,不得复制到源码、日志、普通文档或回复。正式 IMG 的发布目录 `README.md` 是唯一例外:它必须写明该未修改镜像内故意公开的默认账户与 Wi-Fi 四项值,并警告用户修改;不得把当前设备或开发机凭据误写为镜像默认值。 +- 当前设备和开发机凭据只保存在 `测试相关资料/核桃派的用户名和密码和ip/用户名密码ip.txt`,不得复制到源码、日志、普通文档或回复。正式 IMG 的发布目录 `README.md` 是唯一例外:它必须写明该未修改镜像内故意公开的默认账户与 Wi-Fi 四项值,并警告用户修改;不得把当前设备或开发机凭据误写为镜像默认值。 ## 2. 需求与测试编号 @@ -20,7 +20,7 @@ - `HW-*`:核桃派排针、HUB75、ADC、供电和扫描参数。 - `TEST-*`:本机、板端无负载、实屏、ADC、重启、清理和独立性验收。 -新增或改变行为时,先更新 `整体开发需求/` 的稳定编号,再改 `核桃派软件源代码/`,最后同步 `如何测试/` 的映射和合格标准。 +新增或改变行为时,先更新 `整体开发需求/` 的稳定编号,再改 `核桃派软件源代码/`,最后同步 `测试相关资料/如何测试/` 的映射和合格标准。 ## 3. 分层验证 @@ -48,8 +48,8 @@ 3. 每个离线包保存 SHA-256 清单;板端安装使用 `--no-index --find-links` 或明确的本地路径。 4. 不关闭电脑 VPN,不把本机工具目录或盘符固化进项目文档和部署脚本。 5. SSH 后先在远端用 `command -v` 和版本命令检查工具,不能因电脑有工具就假设板端也有。 -6. 普通功能开发不读取或重建大型镜像;只有用户明确要求导出镜像、维护离线材料或更新编辑器时才进入 `离线依赖/` 的发布流程。 -7. 新增或删除板端 Python、Debian 或原生命令依赖时属于功能修改的一部分:必须同步 `离线依赖/其他依赖/` 的文件、SHA-256 和生命周期清单。停用依赖删除二进制并登记原因,不能等待下次镜像导出再补。 +6. 普通功能开发不读取或重建大型镜像;只有用户明确要求导出镜像、维护离线材料或更新编辑器时才进入 `发布更新相关/` 的发布流程。 +7. 新增或删除板端 Python、Debian 或原生命令依赖时属于功能修改的一部分:必须同步 `发布更新相关/其他依赖/` 的文件、SHA-256 和生命周期清单。停用依赖删除二进制并登记原因,不能等待下次镜像导出再补。 8. OTA 和可刷镜像共用 `核桃派软件源代码/VERSION` 与根目录 `发布记录.json`;只有明确导出成功才推进版本,失败不得占号或留下半成品目录。 9. 可部署的功能新增或优化必须同步更新 `核桃派软件源代码/FEATURE_UPDATED_AT`,格式固定为精确到分钟的北京时间 `YYYY-MM-DDTHH:MM+08:00`。单纯 OTA/镜像导出、重复部署和文档修改不得改写该时间。 @@ -79,4 +79,4 @@ - 临时部署、构建目录、wheelhouse 和临时 systemd unit 在验证后按明确绝对路径清理。 - 最终把本目录复制到隔离临时位置,从文档命令重新构建和运行测试;任何引用目录外文件的路径都视为失败。 - 一次性结果写在交付或 `各种归档/<时间戳>_用途/README.md`,不写进可重复测试流程。 -- `离线依赖/导出包/<版本>/` 是可随时删除的大型发布产物,源码、文档命令和后续构建不得引用其中任何文件。 +- `发布更新相关/导出包/<版本>/` 是可随时删除的大型发布产物,源码、文档命令和后续构建不得引用其中任何文件。 --- a/发布更新相关/README.md +++ b/发布更新相关/README.md @@ -1,4 +1,4 @@ -# 离线依赖与发布产物 +# 发布更新材料与产物 本目录用于没有境外网络的核桃派从官方系统镜像离线安装当前软件,并保存按用户明确指令导出的可刷镜像。 @@ -7,6 +7,7 @@ | `核桃派镜像/` | 未修改的官方 RAR、IMG 和摘要 | 稳定基线,不得原地修改 | | `导出包/<版本>/` | Rufus 可写入的版本化 IMG | 可随时删除,项目不得引用 | | `其他依赖/` | AArch64 wheel、Debian 包、预编译内核、固定内核源码和依赖生命周期 | 随软件依赖同步维护 | +| `OTA数据包/<版本>/` | 版本化 OTA 更新包 | 可删除的历史发布产物,不作为后续构建输入 | | `镜像编辑器/` | Windows 编辑器源码和绿色程序 | 编辑器使用独立版本并覆盖旧程序 | 普通开发不需要读取大型镜像或导出目录。只有用户明确要求导出镜像、维护离线依赖或更新编辑器时才进入本流程;但新增或删除软件运行依赖时,必须在同一次功能修改中同步 `其他依赖/`。 --- a/发布更新相关/镜像编辑器/编辑器源代码/README.md +++ b/发布更新相关/镜像编辑器/编辑器源代码/README.md @@ -1,6 +1,8 @@ # 构建镜像配置编辑器 2.0.0 源码使用 Python 3 和 PySide6,不依赖本项目其他目录。支持 Python 3.10–3.14,构建依赖已在 `requirements-build.txt` 锁定。 + +以下命令均在本文件所在的 `发布更新相关/镜像编辑器/编辑器源代码/` 目录执行。 ## Windows 10/11 x64 --- a/发布记录.json +++ b/发布记录.json @@ -6,7 +6,7 @@ "artifact_type": "ota", "created_at": "2026-08-13T15:31:23+08:00", "notes": "新增浏览器全量 OTA。", - "artifact_path": "OTA数据包/1.0.1/matrix-screen-controller-1.0.1.ota", + "artifact_path": "发布更新相关/OTA数据包/1.0.1/matrix-screen-controller-1.0.1.ota", "artifact_sha256": "1ae8544e2072a014dc76e07a930375c80e85b5331957716a456c8123dd204325" }, { @@ -14,7 +14,7 @@ "artifact_type": "image", "created_at": "2026-08-19T15:42:35+08:00", "notes": "建立可刷镜像、离线首次启动和 Windows 镜像编辑能力;修复并真实验证 SSH 默认启用、配置账户密码登录、需同密码完整 sudo 权限、root 登录禁用、sshd 易失运行目录、首启 unit 超时与假成功,以及基础镜像 rootpw、旧 pi 免密规则和主机名解析延迟。", - "artifact_path": "离线依赖/导出包/1.0.2/matrix-screen-controller-1.0.2.img", + "artifact_path": "发布更新相关/导出包/1.0.2/matrix-screen-controller-1.0.2.img", "artifact_sha256": "0397b2abd974cc293a472789027b02bf469434d358578fd43aa9fdb218944aeb" }, { @@ -22,7 +22,7 @@ "artifact_type": "image", "created_at": "2026-08-25T08:17:48+00:00", "notes": "修复 1.0.3 首次启动 FAT 空间不足:应用离线载荷迁入 rootfs,增加候选内核 boot 文件预算与 32 MiB 安全余量双重校验,并修复导出入口误加载 Pillow/FontTools。", - "artifact_path": "离线依赖/导出包/1.0.3/matrix-screen-controller-1.0.3.img", + "artifact_path": "发布更新相关/导出包/1.0.3/matrix-screen-controller-1.0.3.img", "artifact_sha256": "a5676576fb63a913ed9e40a02ce206e5ef71f47b1005a98a8758479b51413e0b" }, { @@ -30,7 +30,7 @@ "artifact_type": "ota", "created_at": "2026-09-04T13:14:05+08:00", "notes": "发布当前工作区已完成并通过本机全套测试的软件版本。", - "artifact_path": "OTA数据包/1.0.4/matrix-screen-controller-1.0.4.ota", + "artifact_path": "发布更新相关/OTA数据包/1.0.4/matrix-screen-controller-1.0.4.ota", "artifact_sha256": "ed15498e2d66758343c874d00176e2525e67a69fea5cc8f4578f139dd2b0f766" }, { @@ -38,7 +38,7 @@ "artifact_type": "ota", "created_at": "2026-09-06T21:10:58+08:00", "notes": "修复 OTA 板端测试目录隔离,并新增可查看、复制的详细失败日志。", - "artifact_path": "OTA数据包/1.0.5/matrix-screen-controller-1.0.5.ota", + "artifact_path": "发布更新相关/OTA数据包/1.0.5/matrix-screen-controller-1.0.5.ota", "artifact_sha256": "ec245d2ba2eefdf415f6745aefb1dfc66631e027d6820a2b6bd04bd6306d4152" }, { @@ -46,7 +46,7 @@ "artifact_type": "ota", "created_at": "2026-09-06T21:39:45+08:00", "notes": "诊断引导包:经设备持有者授权,仅在 1.0.3 旧 worker 中跳过 pytest,以先安装可查看、复制的 OTA 失败日志;后续更新恢复严格测试。", - "artifact_path": "OTA数据包/1.0.6/matrix-screen-controller-1.0.6.ota", + "artifact_path": "发布更新相关/OTA数据包/1.0.6/matrix-screen-controller-1.0.6.ota", "artifact_sha256": "8e804dcfb1f2f9194f3c57b88b5d4b7308509270fba536059e528ab21fdfa0b9" }, { @@ -54,7 +54,7 @@ "artifact_type": "ota", "created_at": "2026-09-08T10:19:47+08:00", "notes": "修复 OTA 显式停止旧服务时运行目录被删除导致升级和回滚中断;增加停服前事务保护、日志降级及真实 systemd 回归。已在测试设备完成 1.0.6 → 1.1.0,用户数据和 frp 状态保持。", - "artifact_path": "OTA数据包/1.1.0/matrix-screen-controller-1.1.0.ota", + "artifact_path": "发布更新相关/OTA数据包/1.1.0/matrix-screen-controller-1.1.0.ota", "artifact_sha256": "7f33a417181633e1f4c77f48a3030d33f6c6e9578002e22f3b0487aff535eef8", "package_kind": "software-install", "required_checkpoint": "1.0.0", @@ -85,7 +85,7 @@ "artifact_type": "image", "created_at": "2026-09-08T07:24:53+00:00", "notes": "完整可刷镜像 1.1.1:默认账户与 Wi-Fi,可直接首启使用", - "artifact_path": "离线依赖/导出包/1.1.1/matrix-screen-controller-1.1.1.img", + "artifact_path": "发布更新相关/导出包/1.1.1/matrix-screen-controller-1.1.1.img", "artifact_sha256": "8356b75ef970891db951a08429f8e2d21a6c0c4933065001778e10d74022c19d", "validation_path": "各种归档/20260908_090953_IMAGE1.1.1实卡验收_bb9884/实机验收.json", "validated_at": "2026-09-08T09:09:53+00:00" --- a/各种归档/20260819_离线镜像1.0.2真实TF卡验收/README.md +++ b/各种归档/20260819_离线镜像1.0.2真实TF卡验收/README.md @@ -3,7 +3,7 @@ ## 失效说明 - 本记录对应 SHA-256 `3a28d9b5200ed22824a628a46ce8d36851cc8366bbf3b8cbd22a2b9a7c4b9128` 的旧 IMG。该镜像最终没有监听 SSH 22 端口,缺少正式导出包的必要功能,因此不得再作为正式包使用。 -- 旧 IMG 已删除,并由同版本的新 IMG 原子替换;新产物摘要以根目录 `发布记录.json` 和 `离线依赖/导出包/1.0.2/manifest.json` 为准。 +- 旧 IMG 已删除,并由同版本的新 IMG 原子替换;新产物摘要以根目录 `发布记录.json` 和 `发布更新相关/导出包/1.0.2/manifest.json` 为准。 - 以下内容只保留当时失败定位和已完成硬件检查的历史证据,不代表新产物已通过真实 TF 卡首启、SSH 或重启验收。新产物须在用户完成镜像编辑、写卡和上电后另行验证。 ## 旧产物当时的结论 @@ -42,4 +42,4 @@ - 镜像构建前必须以 `BASE_IMAGE_PACKAGES.tsv` 验证 `ffmpeg`、`libheif-examples`、`python3.11-venv` 的完整递归闭包;缺根包、传递包、基准清单或摘要时拒绝导出。 - Debian 安装失败保留无配置凭据的 `MSCPKG.TXT`/`MSCPKGS.TSV`;部署失败保留无配置凭据的 `MSCDEPLOY.TXT`;成功时删除这些诊断文件。 - 首启 oneshot 不得同步启动受其 `Before=` 排序约束的控制服务。 -- 本归档是一次性验收记录,不得作为后续构建输入;后续构建不得引用 `离线依赖/导出包/<版本>/`。 +- 本归档是一次性验收记录,不得作为后续构建输入;后续构建不得引用 `发布更新相关/导出包/<版本>/`。 --- a/各种归档/20260825_离线镜像1.0.3启动分区空间修复/README.md +++ b/各种归档/20260825_离线镜像1.0.3启动分区空间修复/README.md @@ -49,4 +49,4 @@ - 正式构建输出 `rootfs bootstrap installed` 与 `rootfs bootstrap verified`;提交后又从 IMG 复制应用 bundle 为独立文件,再次运行只读 rootfs/FAT 验证并通过。 - 新 manifest、侧车摘要、实际整镜像摘要和唯一发布记录四者一致;发布锁、`.building` 和 `.invalid-backup` 均不得残留。 -本记录是一次性故障和发布证据,不得作为后续构建输入;后续构建不得引用 `离线依赖/导出包/1.0.3` 中的任何文件。 +本记录是一次性故障和发布证据,不得作为后续构建输入;后续构建不得引用 `发布更新相关/导出包/1.0.3` 中的任何文件。 --- a/各种归档/20260907_OTA1.1.0软件安装节点验证/README.md +++ b/各种归档/20260907_OTA1.1.0软件安装节点验证/README.md @@ -2,7 +2,7 @@ ## 交付 -- 正式包:`OTA数据包/1.1.0/matrix-screen-controller-1.1.0.ota` +- 正式包:`发布更新相关/OTA数据包/1.1.0/matrix-screen-controller-1.1.0.ota` - 字节数:27993930(约 26.7 MiB)。 - SHA-256:`379f80a30157e23d717157005ea246fa822529010c0f0295dcad5db3ceff42c5`。 - 功能时间:`2026-09-07T22:00+08:00`。导出成功后 VERSION 推进到 1.1.0,发布记录追加一次。 --- a/各种归档/20260908_OTA1.1.0运行目录修复验收/README.md +++ b/各种归档/20260908_OTA1.1.0运行目录修复验收/README.md @@ -2,7 +2,7 @@ 2026-09-08 已在测试设备实际完成 1.0.6 → 1.1.0。磁盘 VERSION、运行 API 均为 1.1.0,OTA worker Result=success,用户数据 421 个非 OTA 文件摘要完全一致,frpc 二进制摘要保持,服务仍 inactive / disabled。组件完整性校验通过,恢复事务、临时保护、恢复 unit/helper、上传包与工作目录均已清理。 -正式产物为 `OTA数据包/1.1.0/matrix-screen-controller-1.1.0.ota`,27998979 字节,SHA-256 为 `7f33a417181633e1f4c77f48a3030d33f6c6e9578002e22f3b0487aff535eef8`。发布时直接复制实机验收候选,逐字节确认一致,未重新构建。VERSION 保持 1.1.0,FEATURE_UPDATED_AT 为 `2026-09-08T10:19+08:00`。旧包摘要 `379f80a30157e23d717157005ea246fa822529010c0f0295dcad5db3ceff42c5`,完整旧产物和旧发布记录归档位置见根发布记录的 repairs 字段。 +正式产物为 `发布更新相关/OTA数据包/1.1.0/matrix-screen-controller-1.1.0.ota`,27998979 字节,SHA-256 为 `7f33a417181633e1f4c77f48a3030d33f6c6e9578002e22f3b0487aff535eef8`。发布时直接复制实机验收候选,逐字节确认一致,未重新构建。VERSION 保持 1.1.0,FEATURE_UPDATED_AT 为 `2026-09-08T10:19+08:00`。旧包摘要 `379f80a30157e23d717157005ea246fa822529010c0f0295dcad5db3ceff42c5`,完整旧产物和旧发布记录归档位置见根发布记录的 repairs 字段。 ## 故障与修复 --- a/整体开发需求/01_网页配置端需求.md +++ b/整体开发需求/01_网页配置端需求.md @@ -6,7 +6,7 @@ ## 0. 需求编号索引 -本文件使用 `WEB-*`、`CONFIG-*` 和 `DEPLOY-*` 编号描述网页端、接口、配置和运行入口。测试覆盖关系维护在 `如何测试/核桃派点阵屏控制服务测试流程.md`,不要在本文件复制完整测试命令。 +本文件使用 `WEB-*`、`CONFIG-*` 和 `DEPLOY-*` 编号描述网页端、接口、配置和运行入口。测试覆盖关系维护在 `测试相关资料/如何测试/核桃派点阵屏控制服务测试流程.md`,不要在本文件复制完整测试命令。 | 编号 | 范围 | 当前阶段要求 | |---|---|---| @@ -34,6 +34,7 @@ | `DEPLOY-IMAGE-EXPORT` | Rufus 可刷镜像 | 从未修改官方 IMG 复制生成;FAT16 只携带双槽配置、元数据和首启程序,Linux 根分区携带应用与已验证预编译内核离线载荷,元数据格式固定为 v3。 | | `DEPLOY-IMAGE-FIRSTBOOT` | 无外设自动首启 | 只连接核桃派本体即可离线安装应用与双内核候选、配置身份和网络、清理秘密并自动重启;SSID、认证、DHCP 或默认路由不可用不阻断安装,真实安装失败恢复原 boot 文件且不循环重启。 | | `DEPLOY-OFFLINE-DEPS` | 离线依赖生命周期 | Python wheel、Debian AArch64 包、预编译内核及固定内核源码均有摘要与用途;代码依赖增删必须同次同步。 | +| `DEPLOY-WORKSPACE-LAYOUT` | 工作区路径与独立性 | 适配分类目录,支持中文、空格及不同调用目录;清理不越界,包内路径与设备路径保持兼容。 | | `DEPLOY-FRP` | FRP 系统组件 | 固定、校验并离线交付官方 Linux ARM64 `frpc`,安装到 `/usr/local/bin`,由维护账户运行且纳入首装、手工更新和 OTA 回滚。 | | `DEPLOY-IMAGE-EDITOR` | 便携镜像编辑器 | Python/PySide6 跨平台源码读取、修改或另存账户、WiFi 和 IPv4;Windows 交付绿色单 EXE,编辑器版本独立于软件版本。 | | `DEPLOY-FONTS` | 多语言字体运行依赖 | 部署环境提供 Fontconfig、Noto Core 和 Noto CJK,服务启动后能解析主流现代文字体系。 | @@ -288,12 +289,12 @@ - OTA 与 SSH 手工更新运行 Python 门槛时必须把应用数据、运行数据、pytest `tmp_path` 和通用临时文件全部限制在本次事务目录,显式设置 `MATRIX_TEST_ROOT`、`TMPDIR` 与 pytest `--basetemp`,并禁用 pytest cache;不得依赖或污染系统默认 `/tmp`。新源码的 conftest 还必须兼容旧更新器只传入 `MATRIX_DATA_DIR`、`MATRIX_RUNTIME_DIR` 和 `MATRIX_SOURCE_ONLY_UPDATE_TESTS=1` 的调用方式。除发布记录中已经固化且不得覆盖的 1.0.6 一次性诊断引导包外,所有当前源码和后续 OTA 都必须实际执行完整 pytest,任一失败继续阻止切换并触发原子回滚;不得保留诊断标记、成功早退或其他测试绕过入口。 - OTA worker 必须把包校验、离线依赖、venv、原生编译、pytest、专用主机检查、迁移、切换、健康检查和回滚的阶段时间、安全主机摘要、命令、退出码及 stdout/stderr 写入单个运行期日志。日志不得读取或记录账户密码、IP、SSID、配置内容、用户资源内容或完整环境变量。失败时以耐久原子写入只保留最近一份、最多 `1 MiB` 的日志,超限时保留头部和最新尾部并标明截断;下一次成功更新删除旧失败日志。 - `GET /api/ota/status` 返回最近失败日志是否可用及字节数;`GET /api/ota/failure-log` 只在最近结果为失败且日志有效时以无缓存 UTF-8 纯文本返回,否则 `404`。设置页检测到带日志的失败后自动打开“OTA 更新失败日志”弹窗,以纯文本等宽滚动区显示,支持 Clipboard API 和非安全局域网 HTTP 下的 textarea 回退复制;关闭后仍保留查看和复制入口,日志读取失败时继续显示原简短错误且不得打开空弹窗。 -- 活动状态原子写入 `/run/matrix-screen-controller/ota-status.json`;持久根 `ota/state.json` 只保留 schema v1 的最近结果和未完成事务恢复信息。不得积累历史包或索引工作区 `OTA数据包/`。 +- 活动状态原子写入 `/run/matrix-screen-controller/ota-status.json`;持久根 `ota/state.json` 只保留 schema v1 的最近结果和未完成事务恢复信息。不得积累历史包或索引工作区 `发布更新相关/OTA数据包/`。 - 新配置字段必须通过逐级迁移增加预设值。删除功能时,迁移只能删除已登记归属该功能的字段或路径;其余配置、字体、模板、动图和未知文件完整复制。成功后删除旧 release、上传包、数据备份和 staging;失败或中途重启按事务记录恢复旧程序、旧 unit 与旧数据。 ### 1.3.3 可刷镜像和离线材料(`DEPLOY-IMAGE-EXPORT`、`DEPLOY-IMAGE-FIRSTBOOT`、`DEPLOY-OFFLINE-DEPS`、`DEPLOY-IMAGE-EDITOR`) -- `离线依赖/核桃派镜像` 只保存未修改官方镜像。导出器复制后向 FAT16 启动分区只写入摘要元数据、一次性 root 首启程序和固定大小双槽配置区;应用源码、AArch64 wheel 与 Debian 包组成的 `MSCBOOT.TGZ` 由 Linux bootstrap 写入复制镜像根分区 `/opt/matrix-image-bootstrap/app/MSCBOOT.TGZ`,预编译内核写入相邻的 `/opt/matrix-image-bootstrap/axp313a`。固定内核源码只留在电脑端,禁止进入 IMG 或 OTA。 +- `发布更新相关/核桃派镜像` 只保存未修改官方镜像。导出器复制后向 FAT16 启动分区只写入摘要元数据、一次性 root 首启程序和固定大小双槽配置区;应用源码、AArch64 wheel 与 Debian 包组成的 `MSCBOOT.TGZ` 由 Linux bootstrap 写入复制镜像根分区 `/opt/matrix-image-bootstrap/app/MSCBOOT.TGZ`,预编译内核写入相邻的 `/opt/matrix-image-bootstrap/axp313a`。固定内核源码只留在电脑端,禁止进入 IMG 或 OTA。 - 镜像元数据格式固定为 v3,记录应用载荷位置、压缩字节数和 SHA-256,以及候选内核 release、压缩/展开字节数、SHA-256、FAT 安装预算和安全余量。IMG 导出必须同时验证预编译载荷及固定源码归档;根分区注入前至少保留“应用压缩载荷 + 内核压缩载荷 + 256 MiB”,注入后重新只读挂载并逐字节核对 FAT v3 元数据、根分区两类载荷和离线依赖元数据。FAT 必须明确不存在 `MSCBOOT.TGZ`。旧 v1/v2 镜像不得原地刷新或迁移,必须从官方基线重建。 - 构建器必须在最终 FAT 布局上按簇取整计算首次安装新增的候选 Image、双份 DTB、System.map、kernel config、受管启动脚本、原始启动脚本回滚副本、临时文件和状态文件,并在这些实际预算之外保留固定 `32 MiB` 安全余量;不满足时在发布前拒绝。内核安装器在写入第一个候选 boot 文件前使用同一算法再次检查当前 `/boot`,不足时保留 rootfs 离线载荷并失败,禁止依靠清除应用载荷换取不可恢复的空间。 - 首启不依赖显示器、键盘、HUB75、ADC 或当前网络在线。它校验并离线安装载荷、编译真实驱动、配置账户和 NetworkManager、生成唯一 machine-id 与 SSH 主机密钥、部署服务,最后安装预编译双内核候选并自动重启一次。镜像配置仍必须包含有效的 WiFi 与 IPv4 字段;首启只写入启用自动连接的受管配置并重启 NetworkManager,不等待 SSID、认证、DHCP 或默认路由,以上任一未就绪都继续离线安装,安装完成后由正常运行服务继续连接并按既有逻辑显示断网提示。NetworkManager 未启动、配置写入失败以及载荷、硬件、权限、SSH 或内核错误仍必须令首启失败。安装内核前根分区至少保留“两倍展开字节数 + 256 MiB”,解包只进入明确的 `/var/tmp/matrix-axp313a-image-install`;成功后删除压缩载荷和展开目录,失败由安装器恢复原 boot 文件、清除部分候选并保留无秘密状态,不联网补包或循环重启。候选启动继续使用一次性健康标记,内核、AXP313A、cpufreq、应用或 GPIO 健康失败时下一次自动回原内核。首启完成时若受管连接或默认路由尚未就绪,FAT 状态仍写成功,但必须明确说明 WiFi 尚未连接且运行系统会继续处理,不得包含 SSID、密码、用户名或地址。 @@ -302,8 +303,14 @@ - 配置 schema v1 固定包含产品、软件版本、账户、WiFi 和 DHCP/静态 IPv4。双槽分别带单调代数、长度和 SHA-256;读取选择最高有效代,保存先完整写入非活动槽并刷新,写入中断必须仍能读取旧槽。 - 编辑器使用不依赖项目外部目录的 Python/PySide6 跨平台源码;Windows 10/11 x64 必须交付内置 Python、Qt 和运行库的唯一绿色单 EXE,其他电脑不得要求安装 Python、.NET、Qt 或补充 DLL。同一源码允许用户在 macOS 本地构建,Windows 发布不得宣称已交付或验收 macOS 产物。编辑器显示镜像版本和当前配置,密码默认遮挡;“修改原镜像”必须二次确认,“另存为”必须使用不同且尚不存在的宿主系统合法 `.img` 文件名,并提示按设备或地点命名。中文、空格和长路径必须受支持;保留名、错误扩展名、同路径、既有目标或无法创建同名摘要时必须在复制或修改前拒绝。未知产品、版本、损坏槽或摘要失败时禁止保存;每次保存以无 BOM UTF-8 生成规范的 `<64 位小写 SHA-256><两个空格><完整文件名>` 侧车文件,按固定字段边界解析并严格复核文件名和镜像内容。摘要必须通过同目录临时文件原子替换,另存失败不得留下 IMG、摘要或临时摘要。 - 编辑器必须声明 Per-Monitor V2 DPI 感知;主窗口、输入区域以及应用内错误、警告、确认和成功提示均按当前显示器 DPI 与工作区自适应缩放,分辨率或显示器变化后保持完整可达。密码输入框与同列普通输入框等宽;低分辨率时允许纵向滚动但不得裁切字段、正文或操作按钮。Windows 原生打开和保存选择器继续使用系统界面。 -- `离线依赖/其他依赖` 是当前软件所需的唯一离线材料清单。新增依赖必须同时提供文件、架构、来源、用途和摘要;停用时删除二进制并记录停用版本与原因。AXP313A 运行载荷必须来自已经通过真实 GPIO 验收的 `6.1.31-matrix-axp313a1`,使用确定性 `tar.gz`,拒绝路径穿越、符号链接、特殊文件、错误 release/commit 和未登记文件;模块的 `build`、`source` 链接不进入载荷,由目标 `depmod` 重建索引。固定源码归档必须与 vendor commit 和补丁摘要同时可离线校验。Debian 包必须相对于登记的官方基础镜像包清单形成可递归解析的完整闭包,镜像构建前自动验证,缺少直接依赖、传递依赖、根包或基准清单时必须拒绝导出。首启 Debian 安装失败时,FAT 状态文件指向不含配置凭据的包诊断与基准包清单,失败后不得继续网络和部署阶段。 -- `离线依赖/导出包/<版本>` 只保存可删除产物,任何源码、测试或后续导出不得引用其中内容。 +- `发布更新相关/其他依赖` 是当前软件所需的唯一离线材料清单。新增依赖必须同时提供文件、架构、来源、用途和摘要;停用时删除二进制并记录停用版本与原因。AXP313A 运行载荷必须来自已经通过真实 GPIO 验收的 `6.1.31-matrix-axp313a1`,使用确定性 `tar.gz`,拒绝路径穿越、符号链接、特殊文件、错误 release/commit 和未登记文件;模块的 `build`、`source` 链接不进入载荷,由目标 `depmod` 重建索引。固定源码归档必须与 vendor commit 和补丁摘要同时可离线校验。Debian 包必须相对于登记的官方基础镜像包清单形成可递归解析的完整闭包,镜像构建前自动验证,缺少直接依赖、传递依赖、根包或基准清单时必须拒绝导出。首启 Debian 安装失败时,FAT 状态文件指向不含配置凭据的包诊断与基准包清单,失败后不得继续网络和部署阶段。 +- `发布更新相关/导出包/<版本>` 只保存可删除产物,任何源码、测试或后续导出不得引用其中内容。 + +### 工作区路径约定(`DEPLOY-WORKSPACE-LAYOUT`) + +- 测试入口位于 `测试相关资料/如何测试/本机测试环境/`,发布工具从 `发布更新相关/` 定位离线材料和产物;源码仍位于根目录 `核桃派软件源代码/`。路径须由脚本位置或显式参数解析,支持中文、空格及不同调用目录。 +- 工作区搬迁不改变设备安装路径、镜像内部载荷目录或历史包协议;已有发布记录仅同步工作区产物路径,不改版本、摘要及历史验收结果。 +- 本机测试与清理必须在隔离工作区验证:能够定位本地源码,保留 `.venv` 和人工持久数据,并拒绝清理越界路径。覆盖见 `TEST-WORKSPACE-LAYOUT`。 ## 2. 页面功能 @@ -1209,8 +1216,7 @@ ## 9. 参考资料 -- 本地资料:`点阵屏幕相关资料/RGB-Matrix-P3-64x64-F_核桃派ZeroW控制说明.md` -- 本地资料:`点阵屏幕相关资料/点阵屏幕_GPIO连接简表_开发用.md` +- 本地接线资料:`硬件相关资料和硬件的连接/点阵屏幕相关资料/手动接线说明/核桃派ZeroW_HUB75_接线与首次上电检查.md` - 核桃派官方 `gpioc`(MIT):https://github.com/walnutpi/gpioc - FastAPI WebSockets:https://fastapi.tiangolo.com/advanced/websockets/ - FastAPI StaticFiles:https://fastapi.tiangolo.com/tutorial/static-files/ --- a/整体开发需求/02_屏幕底层控制接口需求.md +++ b/整体开发需求/02_屏幕底层控制接口需求.md @@ -16,7 +16,7 @@ ## 0. 需求编号索引 -本文件使用 `DISPLAY-*`、`CONFIG-*` 和 `HW-*` 编号描述显示边界、驱动、配置和硬件契约。网页/API 入口见 `01_网页配置端需求.md`,测试覆盖关系见 `如何测试/核桃派点阵屏控制服务测试流程.md`。 +本文件使用 `DISPLAY-*`、`CONFIG-*` 和 `HW-*` 编号描述显示边界、驱动、配置和硬件契约。网页/API 入口见 `01_网页配置端需求.md`,测试覆盖关系见 `测试相关资料/如何测试/核桃派点阵屏控制服务测试流程.md`。 | 编号 | 范围 | 当前阶段要求 | |---|---|---| @@ -604,7 +604,7 @@ - FastAPI 进程内的独立电压监测组件负责持续读取和缓存,并在锁外把保护指令交给显示输出仲裁器;`DisplayService` 不直接访问 ADC。ADC 故障不得终止显示或驱动生命周期,取得过成功样本后须保持最后保护而不是误解除。 - 每台设备通过 `CONFIG-SCREEN-VOLTAGE` 独立保存一次比例校准;校准只补偿当前 ADC 与测量点的比例误差,不能替代接线、共地和型号检查。 - 当前实现 `DISPLAY-LOW-VOLTAGE-PROTECTION`:校准后电压在 `4.5..4.8V` 线性限亮,严格低于 `4.5V` 覆盖 35% 低电图标,严格高于 `4.8V` 经恢复确认撤销。它不实现过压保护、物理关断、BMS 或核桃派关机。 -- 完整接线、ADS1110 协议、换算公式和官方资料见 `M5Stack Unit ADC v1.1相关内容/`;真实硬件验证映射到 `TEST-ADC-HARDWARE`。 +- 完整接线、ADS1110 协议、换算公式和官方资料见 `硬件相关资料和硬件的连接/M5Stack Unit ADC v1.1相关内容/`;真实硬件验证映射到 `TEST-ADC-HARDWARE`。 ## 11. 后续实现验收清单 @@ -641,10 +641,9 @@ ## 13. 参考资料 -- 本地资料:`点阵屏幕相关资料/RGB-Matrix-P3-64x64-F_核桃派ZeroW控制说明.md` -- 本地资料:`点阵屏幕相关资料/点阵屏幕_GPIO连接简表_开发用.md` -- 本地资料:`M5Stack Unit ADC v1.1相关内容/README.md` -- 本地资料:`M5Stack Unit ADC v1.1相关内容/02_ADS1110使用与开发约定_给Codex.md` +- 本地接线资料:`硬件相关资料和硬件的连接/点阵屏幕相关资料/手动接线说明/核桃派ZeroW_HUB75_接线与首次上电检查.md` +- 本地资料:`硬件相关资料和硬件的连接/M5Stack Unit ADC v1.1相关内容/README.md` +- 本地资料:`硬件相关资料和硬件的连接/M5Stack Unit ADC v1.1相关内容/02_ADS1110使用与开发约定_给Codex.md` - 核桃派官方 `gpioc`(MIT):https://github.com/walnutpi/gpioc - 核桃派 ZeroW 参数:https://wiki.walnutpi.com/docs/walnutpi_1/intro/hw-parameter/ - H616/H618 PIO 寄存器依据:板端 device tree `allwinner,sun50i-h616-pinctrl` 与项目内上游来源记录 --- a/核桃派软件源代码/README.md +++ b/核桃派软件源代码/README.md @@ -3,6 +3,8 @@ 本目录是核桃派 ZeroW/H618 的独立服务源码,控制一块 64×64、1/32 扫描、ABCDE 行寻址的 HUB75 RGB 点阵屏。生产驱动是项目自带的 `walnutpi-h618-hub75`,不依赖其他开发板的 GPIO 库。 ## 路径与运行方式 + +下列源码运行、编译和手工部署命令均在本文件所在的 `核桃派软件源代码/` 目录执行;发布命令另行标明从工作区根目录执行。Windows 本机测试使用根目录下 `测试相关资料/如何测试/本机测试环境/README.md` 的命令。 - 程序:`/opt/matrix-screen-controller` - 持久数据:`/var/lib/matrix-screen-controller` @@ -18,11 +20,11 @@ MATRIX_DRIVER=mock .venv/bin/python -m uvicorn app.main:app --host 127.0.0.1 --port 8080 ``` -核桃派不具备境外网络条件。生产安装必须使用项目根目录 `离线依赖/其他依赖/aarch64-py311/` 中已校验的 wheel,并强制 `--no-index`: +核桃派不具备境外网络条件。生产安装必须使用项目根目录 `发布更新相关/其他依赖/aarch64-py311/` 中已校验的 wheel,并强制 `--no-index`: ```bash python3 -m venv .venv -.venv/bin/pip install --no-index --find-links ../离线依赖/其他依赖/aarch64-py311 -r requirements.txt +.venv/bin/pip install --no-index --find-links ../发布更新相关/其他依赖/aarch64-py311 -r requirements.txt make -C app/display/native clean all ``` @@ -51,7 +53,7 @@ sudo app/display/native/hub75_benchmark --duration 60 --refresh-rate 100 --brightness 40 --dev-mem ``` -接屏门槛与全部人工停顿顺序见项目上层 `如何测试/核桃派点阵屏控制服务测试流程.md`。 +接屏门槛与全部人工停顿顺序见项目上层 `测试相关资料/如何测试/核桃派点阵屏控制服务测试流程.md`。 ## 闪烁修复状态 @@ -88,7 +90,7 @@ python ".\核桃派软件源代码\scripts\export_release.py" ota --notes "本次更新说明" ``` -OTA 输出为 `OTA数据包/<版本>/`;镜像输出为 `离线依赖/导出包/<版本>/`。已有版本目录不会覆盖;删除任一旧版本目录不影响项目或设备,后续构建不得引用历史产物。 +OTA 输出为 `发布更新相关/OTA数据包/<版本>/`;镜像输出为 `发布更新相关/导出包/<版本>/`。已有版本目录不会覆盖;删除任一旧版本目录不影响项目或设备,后续构建不得引用历史产物。 ## 可刷镜像导出与同版本修复 --- a/核桃派软件源代码/kernel/README.md +++ b/核桃派软件源代码/kernel/README.md @@ -14,11 +14,11 @@ `walnutpi-linux-6.1.31-30ff3fd5.tar.gz`; its measured SHA-256 is `8659bb3d64313c4693c3605374167a8145186e5c9d43eb771a52a7359699302f`. The fixed archive is registered at -`离线依赖/其他依赖/kernel-source/walnutpi-linux-6.1.31-30ff3fd5/` so the kernel can be +`发布更新相关/其他依赖/kernel-source/walnutpi-linux-6.1.31-30ff3fd5/` so the kernel can be rebuilt without network access. It is a build input and is never copied into an IMG or OTA. The exact production files accepted by the browser and real HUB75 GPIO tests are registered at -`离线依赖/其他依赖/aarch64-kernel/6.1.31-matrix-axp313a1/`. Future IMG exports install this +`发布更新相关/其他依赖/aarch64-kernel/6.1.31-matrix-axp313a1/`. Future IMG exports install this precompiled payload and do not rebuild the kernel unless patches, DTS, or kernel configuration change. --- a/核桃派软件源代码/scripts/build_ota_package.py +++ b/核桃派软件源代码/scripts/build_ota_package.py @@ -25,15 +25,15 @@ args = parser.parse_args() source_root = Path(__file__).resolve().parents[1] project_root = source_root.parent - wheelhouse = project_root / "离线依赖" / "其他依赖" / "aarch64-py311" - frpc_bundle = project_root / "离线依赖" / "其他依赖" / "frp" / "0.71.0" / "linux-arm64" + wheelhouse = project_root / "发布更新相关" / "其他依赖" / "aarch64-py311" + frpc_bundle = project_root / "发布更新相关" / "其他依赖" / "frp" / "0.71.0" / "linux-arm64" version = read_software_version(source_root) policy = read_policy(source_root) if policy["checkpoints"] and version < type(version).parse(policy["checkpoints"][-1]["version"]): parser.error("源码包含未发布的软件依赖;请使用 export_release.py ota --version 导出安装节点") if version.patch != 0: frpc_bundle = None - output_root = (args.output_root or project_root / "OTA数据包").resolve() + output_root = (args.output_root or project_root / "发布更新相关" / "OTA数据包").resolve() version_dir = output_root / str(version) if version_dir.exists(): parser.error(f"version output already exists and will not be overwritten: {version_dir}") --- a/核桃派软件源代码/scripts/deploy_walnutpi.sh +++ b/核桃派软件源代码/scripts/deploy_walnutpi.sh @@ -8,8 +8,8 @@ SOURCE_ROOT=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd) PROJECT_ROOT=$(CDPATH= cd -- "$SOURCE_ROOT/.." && pwd) -WHEELHOUSE=${WHEELHOUSE:-$PROJECT_ROOT/离线依赖/其他依赖/aarch64-py311} -FRPC_BUNDLE=${FRPC_BUNDLE:-$PROJECT_ROOT/离线依赖/其他依赖/frp/0.71.0/linux-arm64} +WHEELHOUSE=${WHEELHOUSE:-$PROJECT_ROOT/发布更新相关/其他依赖/aarch64-py311} +FRPC_BUNDLE=${FRPC_BUNDLE:-$PROJECT_ROOT/发布更新相关/其他依赖/frp/0.71.0/linux-arm64} DEFER_SERVICE_START=${DEFER_SERVICE_START:-0} MANIFEST=$WHEELHOUSE/SHA256SUMS TARGET=/opt/matrix-screen-controller --- a/核桃派软件源代码/scripts/export_release.py +++ b/核桃派软件源代码/scripts/export_release.py @@ -87,8 +87,8 @@ "artifact_type": "ota", "created_at": "2026-08-13T15:31:23+08:00", "notes": "新增浏览器全量 OTA。", - "artifact_path": "OTA数据包/1.0.1/matrix-screen-controller-1.0.1.ota", - "artifact_sha256": sha256_file(path.parent / "OTA数据包" / "1.0.1" / "matrix-screen-controller-1.0.1.ota"), + "artifact_path": "发布更新相关/OTA数据包/1.0.1/matrix-screen-controller-1.0.1.ota", + "artifact_sha256": sha256_file(path.parent / "发布更新相关" / "OTA数据包" / "1.0.1" / "matrix-screen-controller-1.0.1.ota"), } ], } @@ -146,7 +146,7 @@ if len(matches) != 1: raise ValueError("current version must have exactly one release record before repair") index, record = matches[0] - expected_path = f"离线依赖/导出包/{version}/{artifact_name}" + expected_path = f"发布更新相关/导出包/{version}/{artifact_name}" if ( record.get("artifact_type") != "image" or record.get("artifact_path") != expected_path @@ -246,10 +246,10 @@ target = current if args.repair_current else (args.version or next_patch(current)) dependency_bundle = None if not args.repair_current and args.kind == "ota": - dependency_bundle = export_bundle(source, project / "离线依赖" / "其他依赖", current, target) + dependency_bundle = export_bundle(source, project / "发布更新相关" / "其他依赖", current, target) if args.kind == "image" and not args.repair_current and target <= current: parser.error("image candidate version must be newer than the current version") - output_root = project / "OTA数据包" if args.kind == "ota" else project / "离线依赖" / "导出包" + output_root = project / "发布更新相关" / "OTA数据包" if args.kind == "ota" else project / "发布更新相关" / "导出包" official_directory = output_root / str(target) final_directory = args.candidate_output.resolve() if args.candidate_output is not None else official_directory if args.candidate_output is not None: @@ -299,7 +299,7 @@ artifact = temporary_directory / f"matrix-screen-controller-{target}.ota" info = build_package( staged_source, - project / "离线依赖" / "其他依赖" / "aarch64-py311", + project / "发布更新相关" / "其他依赖" / "aarch64-py311", artifact, version=target, release_notes=args.notes, @@ -334,29 +334,29 @@ bootstrap_bundle = Path(staging_text) / "MSCBOOT.TGZ" kernel_dependency = ( project - / "离线依赖" + / "发布更新相关" / "其他依赖" / "aarch64-kernel" / "6.1.31-matrix-axp313a1" ) kernel_source = ( project - / "离线依赖" + / "发布更新相关" / "其他依赖" / "kernel-source" / "walnutpi-linux-6.1.31-30ff3fd5" ) manifest = build_image( - project / "离线依赖" / "核桃派镜像" / "2025-3-6_V2.5.1_WalnutPi-1B_6.1.31_debian12_server.img", + project / "发布更新相关" / "核桃派镜像" / "2025-3-6_V2.5.1_WalnutPi-1B_6.1.31_debian12_server.img", staged_source, - project / "离线依赖" / "其他依赖" / "aarch64-py311", - project / "离线依赖" / "其他依赖" / "debian12-aarch64", + project / "发布更新相关" / "其他依赖" / "aarch64-py311", + project / "发布更新相关" / "其他依赖" / "debian12-aarch64", kernel_dependency, kernel_source, config, artifact, bootstrap_bundle, - project / "离线依赖" / "其他依赖" / "frp" / "0.71.0" / "linux-arm64", + project / "发布更新相关" / "其他依赖" / "frp" / "0.71.0" / "linux-arm64", ) subprocess.run( [ @@ -419,7 +419,7 @@ "artifact_type": args.kind, "created_at": manifest["created_at"], "notes": args.notes.strip(), - "artifact_path": f"{'OTA数据包' if args.kind == 'ota' else '离线依赖/导出包'}/{target}/{artifact.name}", + "artifact_path": f"{'发布更新相关/OTA数据包' if args.kind == 'ota' else '发布更新相关/导出包'}/{target}/{artifact.name}", "artifact_sha256": manifest["image_sha256"] if args.kind == "image" else manifest["artifact_sha256"], **(release_metadata(target) if args.kind == "ota" else {}), **({"component_checkpoints": policy["checkpoints"]} if args.kind == "ota" else {}), --- a/核桃派软件源代码/scripts/install_frpc_system.sh +++ b/核桃派软件源代码/scripts/install_frpc_system.sh @@ -12,7 +12,7 @@ SOURCE_ROOT=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd) PROJECT_ROOT=$(CDPATH= cd -- "$SOURCE_ROOT/.." && pwd) -BUNDLE=${FRPC_BUNDLE:-$PROJECT_ROOT/离线依赖/其他依赖/frp/0.71.0/linux-arm64} +BUNDLE=${FRPC_BUNDLE:-$PROJECT_ROOT/发布更新相关/其他依赖/frp/0.71.0/linux-arm64} UNIT_SOURCE=$SOURCE_ROOT/systemd/matrix-screen-frpc.service UNIT=/etc/systemd/system/matrix-screen-frpc.service DROPIN_DIR=/etc/systemd/system/matrix-screen-frpc.service.d --- a/核桃派软件源代码/scripts/promote_image_release.py +++ b/核桃派软件源代码/scripts/promote_image_release.py @@ -106,7 +106,7 @@ raise ValueError("image candidate README does not match its embedded defaults") _validate_report(validation, digest, version) - dependency_root = project / "离线依赖" / "其他依赖" + dependency_root = project / "发布更新相关" / "其他依赖" kernel = dependency_root / "aarch64-kernel" / "6.1.31-matrix-axp313a1" with tempfile.TemporaryDirectory(prefix="matrix-image-promotion-") as temporary_text: temporary = Path(temporary_text) @@ -133,7 +133,7 @@ history = _history(history_path) if any(record.get("version") == str(version) for record in history["releases"]): raise ValueError("validated image version is already registered") - final = project / "离线依赖" / "导出包" / str(version) + final = project / "发布更新相关" / "导出包" / str(version) if final.exists(): raise ValueError("formal image release directory already exists") stamp = datetime.now(timezone.utc).astimezone().isoformat(timespec="seconds") @@ -149,7 +149,7 @@ "artifact_type": "image", "created_at": manifest["created_at"], "notes": notes.strip(), - "artifact_path": f"离线依赖/导出包/{version}/{artifact_name}", + "artifact_path": f"发布更新相关/导出包/{version}/{artifact_name}", "artifact_sha256": digest, "validation_path": f"{archive.relative_to(project).as_posix()}/实机验收.json", "validated_at": stamp, --- a/核桃派软件源代码/scripts/repair_ota_release.py +++ b/核桃派软件源代码/scripts/repair_ota_release.py @@ -23,7 +23,7 @@ os.close(fd) try: version = read_software_version(source) - current = project / 'OTA数据包' / str(version) + current = project / '发布更新相关' / 'OTA数据包' / str(version) name = f'matrix-screen-controller-{version}.ota' history_path = project / '发布记录.json' history = _history(history_path) --- a/核桃派软件源代码/scripts/update_walnutpi.sh +++ b/核桃派软件源代码/scripts/update_walnutpi.sh @@ -12,8 +12,8 @@ SOURCE_ROOT=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd) PROJECT_ROOT=$(CDPATH= cd -- "$SOURCE_ROOT/.." && pwd) -WHEELHOUSE=${WHEELHOUSE:-$PROJECT_ROOT/离线依赖/其他依赖/aarch64-py311} -FRPC_BUNDLE=${FRPC_BUNDLE:-$PROJECT_ROOT/离线依赖/其他依赖/frp/0.71.0/linux-arm64} +WHEELHOUSE=${WHEELHOUSE:-$PROJECT_ROOT/发布更新相关/其他依赖/aarch64-py311} +FRPC_BUNDLE=${FRPC_BUNDLE:-$PROJECT_ROOT/发布更新相关/其他依赖/frp/0.71.0/linux-arm64} MANIFEST=$WHEELHOUSE/SHA256SUMS TARGET=/opt/matrix-screen-controller DATA_ROOT=/var/lib/matrix-screen-controller --- a/核桃派软件源代码/tests/test_kernel_artifact_dependency.py +++ b/核桃派软件源代码/tests/test_kernel_artifact_dependency.py @@ -96,8 +96,8 @@ def test_registered_kernel_and_source_dependencies_are_complete(): - kernel = PROJECT_ROOT / "离线依赖/其他依赖/aarch64-kernel/6.1.31-matrix-axp313a1" - source = PROJECT_ROOT / "离线依赖/其他依赖/kernel-source/walnutpi-linux-6.1.31-30ff3fd5" + kernel = PROJECT_ROOT / "发布更新相关/其他依赖/aarch64-kernel/6.1.31-matrix-axp313a1" + source = PROJECT_ROOT / "发布更新相关/其他依赖/kernel-source/walnutpi-linux-6.1.31-30ff3fd5" if os.environ.get("MATRIX_SOURCE_ONLY_UPDATE_TESTS") == "1": if not kernel.exists() and not source.exists(): pytest.skip("source-only update payload intentionally omits image build dependencies") @@ -106,7 +106,7 @@ assert info.regular_file_count == 3056 assert info.unpacked_file_bytes == 160051604 assert verify_source_dependency(source)["archive_bytes"] == 249095239 - registry = json.loads((PROJECT_ROOT / "离线依赖/其他依赖/DEPENDENCIES.json").read_text(encoding="utf-8")) + registry = json.loads((PROJECT_ROOT / "发布更新相关/其他依赖/DEPENDENCIES.json").read_text(encoding="utf-8")) active = {item["id"]: item for item in registry["active"]} assert active["axp313a-kernel-runtime"]["path"] == "aarch64-kernel/6.1.31-matrix-axp313a1" assert active["walnutpi-linux-kernel-source"]["path"] == ( --- a/核桃派软件源代码/tests/test_ota_checkpoints.py +++ b/核桃派软件源代码/tests/test_ota_checkpoints.py @@ -187,7 +187,7 @@ source.mkdir() (source / 'VERSION').write_text('1.0.6\n', encoding='utf-8') (source / 'FEATURE_UPDATED_AT').write_text('2026-09-07T20:00+08:00\n', encoding='utf-8') - deps = tmp_path / '离线依赖/其他依赖' + deps = tmp_path / '发布更新相关/其他依赖' binary = deps / 'frp/v/frpc' binary.parent.mkdir(parents=True) binary.write_bytes(b'frpc') @@ -213,11 +213,13 @@ assert (source/'VERSION').read_text(encoding='utf-8').strip() == '1.1.0' monkeypatch.setattr(sys, 'argv', ['export', 'ota', '--notes', 'patch']) assert exporter.main() == 0 - artifact = next((tmp_path/'OTA数据包/1.1.1').glob('*.ota')) + artifact = next((tmp_path/'发布更新相关/OTA数据包/1.1.1').glob('*.ota')) with zipfile.ZipFile(artifact) as z, z.open('payload.tar.gz') as f, tarfile.open(fileobj=f, mode='r|gz') as t: assert not any('system-dependencies' in item.name for item in t) records = json.loads((tmp_path/'发布记录.json').read_text(encoding='utf-8'))['releases'] assert [r['package_kind'] for r in records] == ['software-install', 'application'] + assert all(r['artifact_path'].startswith('发布更新相关/OTA数据包/') for r in records) + assert all((tmp_path / r['artifact_path']).is_file() for r in records) assert (source/'FEATURE_UPDATED_AT').read_text(encoding='utf-8') == '2026-09-07T20:00+08:00\n' @@ -234,7 +236,7 @@ exporter.main() assert (source/'VERSION').read_bytes() == original_version assert (tmp_path/'发布记录.json').read_bytes() == original - assert not list((tmp_path/'OTA数据包').iterdir()) + assert not list((tmp_path/'发布更新相关/OTA数据包').iterdir()) assert not (tmp_path/'.release-export.lock').exists() @@ -268,13 +270,13 @@ exporter, source = release_project(tmp_path, monkeypatch) monkeypatch.setattr(sys, 'argv', ['export', 'ota', '--version', '1.1.0', '--notes', 'original']) exporter.main() - artifact=tmp_path/'OTA数据包/1.1.0/matrix-screen-controller-1.1.0.ota' + artifact=tmp_path/'发布更新相关/OTA数据包/1.1.0/matrix-screen-controller-1.1.0.ota' old=artifact.read_bytes();old_history=(tmp_path/'发布记录.json').read_bytes() (source/'fix.py').write_text('FIXED = True\n',encoding='utf-8') candidate=tmp_path/'candidate.ota' - build_package(source,tmp_path/'离线依赖/其他依赖/aarch64-py311',candidate, + build_package(source,tmp_path/'发布更新相关/其他依赖/aarch64-py311',candidate, version=V.parse('1.1.0'),release_notes='repair', - system_dependencies=tmp_path/'离线依赖/其他依赖/frp/v') + system_dependencies=tmp_path/'发布更新相关/其他依赖/frp/v') report=tmp_path/'validation.json' report.write_text(json.dumps({'package_sha256':hashlib.sha256(candidate.read_bytes()).hexdigest(), 'installed_version':'1.1.0','status':'success','runtime_lifecycle_passed':True, --- a/核桃派软件源代码/tests/test_release_image.py +++ b/核桃派软件源代码/tests/test_release_image.py @@ -133,7 +133,7 @@ assert export_release.main() == 0 assert (source / "VERSION").read_text(encoding="utf-8") == "1.1.0\n" assert history.read_text(encoding="utf-8") == '{"schema_version":1,"releases":[]}' - assert not (tmp_path / "离线依赖" / "导出包" / "1.1.1").exists() + assert not (tmp_path / "发布更新相关" / "导出包" / "1.1.1").exists() assert {path.name for path in candidate.iterdir()} == { "README.md", "manifest.json", "matrix-screen-controller-1.1.1.img", "matrix-screen-controller-1.1.1.img.sha256", --- a/测试相关资料/如何测试/本机测试环境/README.md +++ b/测试相关资料/如何测试/本机测试环境/README.md @@ -5,10 +5,10 @@ ## 常用命令 ```powershell -python ".\如何测试\本机测试环境\local_test.py" setup -python ".\如何测试\本机测试环境\local_test.py" test --suite all -python ".\如何测试\本机测试环境\local_test.py" serve --port 8765 -python ".\如何测试\本机测试环境\local_test.py" clean +python ".\测试相关资料\如何测试\本机测试环境\local_test.py" setup +python ".\测试相关资料\如何测试\本机测试环境\local_test.py" test --suite all +python ".\测试相关资料\如何测试\本机测试环境\local_test.py" serve --port 8765 +python ".\测试相关资料\如何测试\本机测试环境\local_test.py" clean ``` 测试套件: --- a/测试相关资料/如何测试/本机测试环境/local_test.py +++ b/测试相关资料/如何测试/本机测试环境/local_test.py @@ -11,7 +11,7 @@ HARNESS_DIR = Path(__file__).resolve().parent -WORKSPACE_ROOT = HARNESS_DIR.parent.parent +WORKSPACE_ROOT = HARNESS_DIR.parents[2] SOURCE_DIR = WORKSPACE_ROOT / "核桃派软件源代码" VENV_DIR = HARNESS_DIR / ".venv" WORK_DIR = HARNESS_DIR / "work" --- a/测试相关资料/如何测试/核桃派点阵屏控制服务测试流程.md +++ b/测试相关资料/如何测试/核桃派点阵屏控制服务测试流程.md @@ -6,6 +6,7 @@ | 测试编号 | 主要覆盖 | 执行位置 | 人工停顿 | |---|---|---|---| +| `TEST-WORKSPACE-LAYOUT` | `DEPLOY-WORKSPACE-LAYOUT`、测试与发布路径、隔离和清理 | 本机中文及空格路径 | 否 | | `TEST-LOCAL-MOCK` | `WEB-*`、`DISPLAY-MOCK`、`CONFIG-*` | Windows 或隔离 Linux | 否 | | `TEST-NATIVE-UNIT` | `DISPLAY-DRIVER`、bitplane、映射、亮度、双缓冲 | 本机,不访问 `/dev/mem` | 否 | | `TEST-REMOTE-HEALTH` | `DEPLOY-*`、系统、网络、磁盘、工具 | 核桃派,无屏 | 否 | @@ -42,8 +43,8 @@ 从独立项目根执行: ```powershell -python ".\如何测试\本机测试环境\local_test.py" setup -python ".\如何测试\本机测试环境\local_test.py" test +python ".\测试相关资料\如何测试\本机测试环境\local_test.py" setup +python ".\测试相关资料\如何测试\本机测试环境\local_test.py" test ``` 前端测试: @@ -97,7 +98,9 @@ 候选晋升的最低真实硬件门槛是同一摘要 IMG 在默认热点从上电起可用时完成一次完整正向首启、自动重启、SSH/sudo/root 拒绝、服务、内核、H618、网页和再次重启复验。SSID 不存在、认证失败和 DHCP 失败分支仍须通过自动化;没有另行执行真实故障网络矩阵时,在验收记录中明确写为“本次未重复实卡验证”,不得写成实卡通过。 -`TEST-OTA-REAL` 固定走 `1.0.0 → 1.0.1`:先用手工更新入口部署 1.0.0,再从系统设置选择工作区 `OTA数据包/1.0.1/` 中的真实包。更新前后对持久根做文件清单,除 schema 迁移和单条 OTA 状态外保持一致;确认版本、实例 ID、真实驱动、硬件映射、PWM4 OE 后端和 fault 计数。再次上传同一包必须在停服前拒绝。成功后不得残留旧 release、上传包、staging、数据备份或活动 OTA unit。另用受控失败包确认 1.0.5 及后续旧版本服务恢复后自动弹出最近失败日志,日志包含失败用例与回滚结论且可在局域网 HTTP 页面完整复制;成功更新清除旧日志。1.0.6 是发布记录中唯一经授权跳过旧 worker pytest 的历史诊断引导包,只用于先安装日志能力,其成品不得覆盖、实现不得复制到当前源码或任何后续包;1.0.7 及以后全部恢复严格 pytest 门槛。实屏应在更新期间以 40% 亮度显示整体缩放为 58×58、位于 `(3,3)` 的居中 OTA 进度,四边均有黑色空白,并在成功后恢复默认内容;摄像头画面不清晰时必须等待用户肉眼确认。 +历史 OTA 验收的前提是备齐对应版本的原始包及其摘要;历史产物允许按生命周期删除。缺少所需版本时应报告“验收材料缺失”,停止该历史版本组合的验收,不把路径存在性失败记为更新失败,也不自动导出或替换成其他版本。 + +`TEST-OTA-REAL` 固定走 `1.0.0 → 1.0.1`:先用手工更新入口部署 1.0.0,再从系统设置选择工作区 `发布更新相关/OTA数据包/1.0.1/` 中的真实包。更新前后对持久根做文件清单,除 schema 迁移和单条 OTA 状态外保持一致;确认版本、实例 ID、真实驱动、硬件映射、PWM4 OE 后端和 fault 计数。再次上传同一包必须在停服前拒绝。成功后不得残留旧 release、上传包、staging、数据备份或活动 OTA unit。另用受控失败包确认 1.0.5 及后续旧版本服务恢复后自动弹出最近失败日志,日志包含失败用例与回滚结论且可在局域网 HTTP 页面完整复制;成功更新清除旧日志。1.0.6 是发布记录中唯一经授权跳过旧 worker pytest 的历史诊断引导包,只用于先安装日志能力,其成品不得覆盖、实现不得复制到当前源码或任何后续包;1.0.7 及以后全部恢复严格 pytest 门槛。实屏应在更新期间以 40% 亮度显示整体缩放为 58×58、位于 `(3,3)` 的居中 OTA 进度,四边均有黑色空白,并在成功后恢复默认内容;摄像头画面不清晰时必须等待用户肉眼确认。 ## 2. 原生驱动单元测试 @@ -240,3 +243,9 @@ 完整包仍执行全部 pytest。涉及主服务停启的 OTA 修复必须走真实设备 OTA,核对磁盘与 API 版本、成功状态、持久数据、frp 启停和事务清理。systemctl 替身仅能验证业务分支,不能作为真实目录生命周期或 OTA 通过证据。显示效果与软件验收分开记录,缺少人工/摄像头证据时不能记为实屏通过。 同版本修复覆盖验证报告不匹配、候选源码不匹配、历史摘要损坏、提交失败回退及原始包完整归档;正式产物必须与实机验收候选包摘要一致,不得在实机成功后重新构建替换。 + +## 工作区目录适配验收(`TEST-WORKSPACE-LAYOUT`) + +从工作区根目录及其他调用目录分别通过测试入口绝对路径执行测试;复制源码、测试入口、当前需求和必要离线材料到含中文及空格的隔离目录,按本文件命令运行 Python、前端与 mock 健康检查。复制件不得依赖原工作区或历史导出产物。发布定位用临时夹具测试,确认依赖来自 `发布更新相关/其他依赖/`,OTA 与 IMG 输出分别位于 `发布更新相关/` 下的 `OTA数据包/`、`导出包/`;不为路径验证推进正式版本。 + +在隔离目录执行 `clean`,确认 `.venv`、依赖指纹和人工持久数据保持不变,仅删除登记的临时目标;越界路径必须拒绝。核对现存文档链接和发布记录中的产物路径,已删除的历史产物允许缺失,须明确其历史性质,不重新生成。镜像内部 `project/离线依赖/` 属于载荷协议,构建器与首启脚本应继续保持一致。 --- a/硬件相关资料和硬件的连接/M5Stack Unit ADC v1.1相关内容/02_ADS1110使用与开发约定_给Codex.md +++ b/硬件相关资料和硬件的连接/M5Stack Unit ADC v1.1相关内容/02_ADS1110使用与开发约定_给Codex.md @@ -113,16 +113,16 @@ ```bash # 本机纯计算/协议自测,不访问 I²C,Windows 也可以运行 -python "M5Stack Unit ADC v1.1相关内容/示例程序/read_unit_adc_v1_1.py" --self-test +python "硬件相关资料和硬件的连接/M5Stack Unit ADC v1.1相关内容/示例程序/read_unit_adc_v1_1.py" --self-test # 核桃派单次读数 -python3 "M5Stack Unit ADC v1.1相关内容/示例程序/read_unit_adc_v1_1.py" +python3 "硬件相关资料和硬件的连接/M5Stack Unit ADC v1.1相关内容/示例程序/read_unit_adc_v1_1.py" # 连续读数 -python3 "M5Stack Unit ADC v1.1相关内容/示例程序/read_unit_adc_v1_1.py" --watch +python3 "硬件相关资料和硬件的连接/M5Stack Unit ADC v1.1相关内容/示例程序/read_unit_adc_v1_1.py" --watch # 使用一次比例校正 -python3 "M5Stack Unit ADC v1.1相关内容/示例程序/read_unit_adc_v1_1.py" --calibration-factor 1.00604 --watch +python3 "硬件相关资料和硬件的连接/M5Stack Unit ADC v1.1相关内容/示例程序/read_unit_adc_v1_1.py" --calibration-factor 1.00604 --watch ``` 额外参数通过 `--help` 查看。独立工具必须保持 Python 标准库实现并与主服务监测器分开;不能把独立无限循环直接放进 FastAPI 进程,也不能与主服务同时长期读取同一地址。 --- a/硬件相关资料和硬件的连接/M5Stack Unit ADC v1.1相关内容/官方资料/来源索引.md +++ b/硬件相关资料和硬件的连接/M5Stack Unit ADC v1.1相关内容/官方资料/来源索引.md @@ -15,7 +15,7 @@ 重新核验哈希时,在 PowerShell 中运行: ```powershell -Get-ChildItem -LiteralPath ".\M5Stack Unit ADC v1.1相关内容\官方资料" -Filter *.pdf | +Get-ChildItem -LiteralPath ".\硬件相关资料和硬件的连接\M5Stack Unit ADC v1.1相关内容\官方资料" -Filter *.pdf | Get-FileHash -Algorithm SHA256 ``` --- /dev/null +++ b/核桃派软件源代码/tests/test_workspace_layout.py @@ -0,0 +1,83 @@ +from __future__ import annotations + +import importlib.util +import os +from pathlib import Path +import shutil +import subprocess +import sys + +import pytest + + +PROJECT_ROOT = Path(__file__).resolve().parents[2] +HARNESS_RELATIVE = Path("测试相关资料/如何测试/本机测试环境/local_test.py") + + +@pytest.fixture +def relocated_harness(tmp_path): + original = PROJECT_ROOT / HARNESS_RELATIVE + if not original.is_file() and os.environ.get("MATRIX_SOURCE_ONLY_UPDATE_TESTS") == "1": + pytest.skip("source-only update payload intentionally omits the workstation harness") + root = tmp_path / "独立 工作区" + script = root / HARNESS_RELATIVE + script.parent.mkdir(parents=True) + shutil.copyfile(original, script) + source = root / "核桃派软件源代码" + (source / "app").mkdir(parents=True) + for name in ("requirements.txt", "requirements-dev.txt"): + (source / name).write_text("", encoding="utf-8") + spec = importlib.util.spec_from_file_location("relocated_local_test", script) + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + return root, module + + +def test_harness_locates_relocated_source_from_unrelated_cwd(relocated_harness, tmp_path): + root, harness = relocated_harness + probe = ( + "import runpy, sys; from pathlib import Path; " + "h = runpy.run_path(sys.argv[1]); h['_validate_layout'](); " + "assert h['WORKSPACE_ROOT'] == Path(sys.argv[2]); " + "assert h['SOURCE_DIR'] == Path(sys.argv[2]) / '核桃派软件源代码'" + ) + subprocess.run( + [sys.executable, "-c", probe, str(root / HARNESS_RELATIVE), str(root)], + cwd=tmp_path, + check=True, + ) + + +def test_clean_keeps_environment_and_manual_data_in_relocated_workspace(relocated_harness): + root, harness = relocated_harness + keep = { + harness.VENV_PYTHON: b"interpreter sentinel", + harness.REQUIREMENTS_STAMP: b"requirements sentinel", + harness.DATA_DIR / "config.json": b"manual config sentinel", + harness.DATA_DIR / "templates" / "example.json": b"manual template sentinel", + } + for path, content in keep.items(): + path.parent.mkdir(parents=True, exist_ok=True) + path.write_bytes(content) + for directory in (harness.WORK_DIR, harness.DATA_DIR / "runtime", harness.SOURCE_DIR / "app/__pycache__"): + directory.mkdir(parents=True) + (directory / "temporary").write_bytes(b"temporary") + harness.clean() + assert all(path.read_bytes() == content for path, content in keep.items()) + assert not harness.WORK_DIR.exists() + assert not (harness.DATA_DIR / "runtime").exists() + assert not (harness.SOURCE_DIR / "app/__pycache__").exists() + with pytest.raises(harness.HarnessError): + harness._assert_within(root.parent / "outside", root) + + +def test_clean_rejects_outside_target_before_deleting(relocated_harness, tmp_path, monkeypatch): + _, harness = relocated_harness + outside = tmp_path / "outside" + outside.mkdir() + marker = outside / "preserve" + marker.write_bytes(b"unchanged") + monkeypatch.setattr(harness, "WORK_DIR", outside) + with pytest.raises(harness.HarnessError): + harness.clean() + assert marker.read_bytes() == b"unchanged"