Files

12 KiB
Raw Permalink Blame History

AGENTS.md — DLPilot 工程档案(供 AI/Agent 使用)

本文档面向 AI/Agent,是人类文档 README.md 的姊妹篇,两者不重叠: README 回答"这是什么、怎么用";本文回答"代码怎么组织、怎么构建发布、改动时哪些约束不能破坏"。 任何 Agent 在修改本目录代码前应先读完本文。

1. 项目定位

仓库 LT8619C_Debug_Tools 是 LT8619CHDMI→RGB888)→ DLPC3421 投影转接板的调试上位机工具集 上游主仓库(固件)为 hulk/LT8619C_DLPC3421_HDMI(同 Gitea 实例 gitea.hulk.wang), 本仓库本机路径 E:\Hulk-Coding\LT8619C_DLPC3421_HDMI。 DLPilot 工作在两端:HDMI 源端(PC 显卡侧,EDID/分辨率/图案)与光机控制端 RS485 → 板载 GD32E230 网关 → DLPC3421)。寄存器语义判读基线(0x606E 位交换 / 0x606D 整组顺序)与光机协议规程都在上游仓库 doc/architecture/protocols.md,不在本仓库。

2. 目录契约(重要)

DLPilot/
├── src/       源代码区 —— 只有这里可以手改(logos/ 改图标源后重跑 make_ico.py
├── assets/    生成资产区 —— gen_*.py 的产物(PNG 图案),可随时再生成,改动需提交
├── release/   产物区 —— 机器生成,禁止手改:DLPilot.exe、pattern_slots.json、
│              bw/solid 图案(package_release.py 从 assets/ 平铺复制来,均入库)、
│              *.zipgitignore,只上传 Release)。release/ 本身=完整可直接运行的
│              发布文件夹(exe+附属平铺),内容与 zip 一致
├── README.md  人类文档
└── AGENTS.md  本文

划分原则:"人写的"在 src/"生成的"按去向分 assets/(随包分发)与 release/(直接交付)。 新文件必须落对区域;不要在 src/ 外新建源码,不要手改 assets/ 下的 PNG(改生成脚本再跑)。

文件角色速查

文件 角色
src/hdmi_gui.py 入口: argparse 命令行分发(--dump/--edid-fields/--test-pattern/--dlpc-cmd/--seconds), GUI 委托 app_gui
src/app_gui.py CustomTkinter 主界面: 双栏仪表盘(左 HDMI 源端五卡/右 光机四卡), 光强/定时快捷滑条
src/display_win.py Win32 显示器枚举/分辨率切换/智能拓扑(纯 ctypes)
src/edid_parse.py EDID 1.4/CEA-861 解析(纯标准库, 上游 lt8619c_edid.c 以此为参考解码器)
src/patterns.py 测试图案全屏投屏窗口(tkinter, ESC 退出, 测试卡区域 1:1 居中)
src/config.py 运行目录定位(BASE_DIR)与 pattern_slots.json 读写(slots/area/serial 三键)
src/dlpc_rs485.py 光机 RS485 协议模块: 帧构造/增量解析/译码表/预设命令/串口线程(DLPC485)
src/selftest_rs485.py 光机协议自检(对照上游实测帧向量, 无硬件回归, 改协议代码后必跑)
src/build_exe.py PyInstaller 打包 → release/DLPilot.exeonefile/noconsole, 内嵌图标+pyserial+customtkinter
src/package_release.py 复制图案到 release/(平铺) → release/DLPilot-v<ver>-win64.zip(校验栏位引用完整性; release/ 与 zip 内容一致)
src/publish_gitea.py 建/复用 Gitea Release 并上传 ziptag 默认取 zip 文件名)
src/hdmi_source.ps1/.bat 独立 PowerShell 命令行工具(自包含,与 GUI 无代码共享)
src/gen_bw_patterns.py 生成 assets/bw_*.png ×5640x360 黑白,需 Pillow
src/gen_solid_colors.py 生成 assets/solid_*.png ×10800x600 纯色,需 Pillow
src/la_wave_analyzer*.py 逻辑分析仪 CSV 波形判读(2/3 通道,40MHz 像素时钟假设)
src/logos/make_ico.py svg/png → ico(写在其自身目录)
release/pattern_slots.json 预置栏位:西门子星/波带片/综合测试卡,area=640x360serial 键记忆光机串口
UI参考布局.svg/.png 界面布局设计稿(app_gui.py 双栏布局的规格来源, 随分支入库)

3. 代码不变量(改 hdmi_gui.py 必须维持)

  1. 依赖分层CLI/显示/EDID/协议/配置模块(display_win/edid_parse/patterns/config/ dlpc_rs485)纯标准库;customtkinter 是 GUIapp_gui)的硬依赖(装了才能开界面, CLI 不受影响)。可选依赖仍走"try-import + HAS_* 标志 + 缺失降级"模式:Pillow、 pyserial——缺失时对应功能禁用/提示安装,其余功能必须照常工作。
  2. 模块边界hdmi_gui.py 只是入口, 不放实现; GUI 不直接碰 ctypes/winreg (显示器走 display_win, EDID 走 edid_parse; pyserial 只准在 dlpc_rs485.py 里 import hdmi_gui/app_gui 经 dlpc_rs485.HAS_SERIALDLPC485 类间接使用)。
  3. BASE_DIR 语义(定义在 config.py):frozenPyInstaller onefile)时 BASE_DIR=exe 所在目录sys.executable),源码运行时 =config.py 所在目录 (即 src/)。所有用户数据(pattern_slots.json、自定义图片)都写/读自 BASE_DIR 平铺存放,不建子目录。
  4. 自定义栏位 file 字段是裸文件名:加载时 os.path.join(BASE_DIR, file) 解析, 因此 pattern_slots.json 引用的图片必须与 exe 同目录 —— package_release.py 正是按此平铺打包。
  5. exe 自包含:图标经 --add-data 内嵌(运行时从 _MEIPASS 读);内置五种图案 (红/绿/蓝/棋盘格/网格线)由程序绘制,运行时不依赖 assets/solid/bw PNG 只服务自定义栏位。
  6. DPI 感知:进程启动即 SetProcessDpiAwareness(2),图案窗口按物理像素定位,勿移除。
  7. 智能显示拓扑(勿改回无条件扩展):启动时枚举"接入但不在桌面"的显示器,仅当其 EDID 厂商字节为转接板签名 61 93(压缩码 "XLS",上游 lt8619c_edid.c)时才 SetDisplayConfig(SDC_TOPOLOGY_EXTEND|SDC_APPLY) 拉回桌面,静默容错。动机:板卡 热插拔后 Windows 可能停留在"仅电脑屏幕"导致枚举不到板卡。用户主动选择的拓扑 (家里的"仅第二屏幕"等,未激活的是笔记本内屏,厂商非 XLS)一律不动。
  8. 分辨率切换是临时的:走 CHANGEDISPLAYSETTINGS(不写注册表),"恢复"用会话开始时快照。
  9. EDID 来源是注册表HKLM\SYSTEM\CurrentControlSet\Enum\DISPLAY\...\Device Parameters\EDID), 不是 WMI/DDCexe 的 noconsole 版无 stdout--dump/--edid-fields 只在源码/控制台版可用。
  10. 光机串口线程回调不直接碰 GUIDLPC485 的 on_event/on_status 在后台线程触发, 必须经 queue.Queue + root.after(80, …) 轮询投递回主线程(现行 _poll_dlpc 模式, 与 I2C-Inject 的 Injector 同源);退出路径(关窗与 --seconds 自动关)都会走到 App.destroy(),在那里 dlpc.close() 收线程。
  11. pattern_slots.json 格式{'area', 'slots', 'serial'} 三键共存;load 兼容旧版 纯数组(无 area)与无 serial 键两种历史格式,save 永远写全三键。serial 子键为 {port, baud, bytesize(8/7/6/5), parity(N/E/O), stopbits(1/2)},旧配置缺子键按 默认 8-N-1 补齐。
  12. GUI = app_gui.py 双栏仪表盘(规格:DLPilot/UI参考布局.svg):左列 HDMI 源端 五卡(显示器/模式/EDID 信息/测试图案/hex),右列光机四卡(串口/数据/命令/滑条), 顶栏 Dark/Light/System 切换(ttk.Treeview 颜色随动)。打包必须 --collect-all customtkinterbuild_exe.py 已内置,勿删)——CTk 主题/字体数据缺失会让 exe 启动即崩。

4. 领域语义(测试值都有含义,勿"顺手改")

  • 纯色值 55/AA/FF55(01010101) 与 AA(10101010) 互为字节镜像对(检出位序颠倒/位交换), FF(11111111) 全高基准;R/G/B 三行对应 RGB888 三字节组(R=D[23:16]/G=D[15:8]/B=D[7:0])。
  • 800x600@60LT8619C demo 板 shadow EDID 首选时序(solid 系列分辨率与之一致)。
  • 640x360:测试卡区域默认值(16:9 半分辨率,点对点检查用);full=铺满所选屏。 同时是转接板光机 DLPC3421 的 nHD 面板原生分辨率(该板 shadow EDID 只声明 640x360@60), 投 640x360 图案 + RS485 开光机 = 整链路 1:1 验证。
  • bw 系列图案各自目的见 src/gen_bw_patterns.py 顶部清单注释。
  • 光机 RS485 命令集(M730/M731/M732/M201/M202/M737/M999/M888/M7/M8/M1)语义、 帧格式 D5 01 LEN <ASCII> CRC(累加和校验)、错误码与就绪门禁 DLPC 未就绪时 M730/M731/M732 被拒回 0xFF)以上游 protocols.md 为唯一权威, 本仓不维护协议文档副本;注意 M7S0 是"开启"TEC 自动控温(方向反直觉)、 M737 在定时投光中延迟应答(CLI 等待上限 35s)。

5. 构建 / 发布流水线(全部在 src/ 下执行)

python gen_bw_patterns.py ; python gen_solid_colors.py   # 仅当改了图案生成逻辑
python build_exe.py            # → ../release/DLPilot.exe(缺 PyInstaller 会自动 pip install
python build_exe.py --debug    # 附加带控制台的 DLPilot-console.exe(调试 --dump 用)
python package_release.py <版本>   # → ../release/DLPilot-v<版本>-win64.zip
python publish_gitea.py DLPilot-v<版本>-win64.zip --replace --insecure   # 上传(需 GITEA_TOKEN)

发布约定:

  • tag/release 命名 DLPilot-vX.Y.Zzip 命名 DLPilot-vX.Y.Z-win64.zippublish_gitea.py 依赖此命名从文件名推 tag);
  • exe、pattern_slots.json 与 release/ 下平铺的图案 png 改动跟随源码/资产生成提交入库; zip 永不入库.gitignore 已挡);
  • publish_gitea.py 幂等:release 已存在则复用,同名资产默认跳过,--replace 删旧重传;
  • token 读环境变量 GITEA_TOKEN:本机已用 setx 持久化为用户级变量(新开的会话/终端自动 继承,值不入库;在 setx 之前启动的进程读不到,需重开终端),急用时也可临时传 --token
  • Gitea 实例 gitea.hulk.wangAPI 走 https(443)git 走 ssh(1234)——从 origin 远程自动解析; python urllib 连该实例会在部分请求上报 SSL: UNEXPECTED_EOF实际发布需加 --insecure
  • 本实例 Gitea 的 API 兼容性怪癖:GET /releases/tags/{tag} 返回的 assets 是纯数组, 而 GET /releases/{id}{items:[...]} —— publish_gitea.py 已按两种形状兼容,勿"简化"回去。

6. 已知坑(历史踩过,勿重蹈)

  • onefile 运行时 __file__ 指向临时解压目录 _MEIPASS,定位 exe 必须用 sys.executable(§3.3);
  • 重新打包前旧 exe 可能被残留进程占用 → build_exe.py 会 taskkill 同名进程,手改打包脚本时保留;
  • 打包后本机图标缓存不刷新:ie4uinit -show,不是产物问题;
  • 改图标流程:改 svg → make_ico.py 生成 ico → 重新打包(ico 同时是文件图标与运行时窗口图标);
  • GUI 与 hdmi_source.ps1 各自独立实现显示器枚举,行为可能微差(GUI 与 -List 序号一致, 但与"设置"面板序号无保证);多屏操作前先确认序号;
  • pattern_slots.json 兼容两种历史格式,写出只写新格式(详见 §3.11);
  • CustomTkinter 6.xCTkComboBox 不支持 textvariable(用 .set()/.get());CTkTextboxtag_config 在部分版本不可用,app_gui 已 try/except 兜底为单色;同一容器内 grid/pack 不可混用(卡片用 grid 挂进列,卡片内部一律 pack——_card 返回 (卡片, 内容));

7. 验证清单(改完代码后)

  1. python -m py_compile src/*.py —— 全模块可编译;
  2. python selftest_rs485.py —— 光机协议回归(改 dlpc_rs485.py 后必跑,向量来自上游 protocols.md);
  3. python hdmi_gui.py --seconds 5 —— GUI 冒烟(自动开关);
  4. python hdmi_gui.py --test-pattern Checker --area 800x600 --seconds 3 —— 投图链路;
  5. python hdmi_gui.py --dump —— EDID 解算不抛异常;
  6. python hdmi_gui.py --dlpc-cmd M999(不带 --port)—— 应明确报错而非崩溃(无硬件时的优雅降级);
  7. python build_exe.py → 双击 release/DLPilot.exe 确认图标/双栏布局/投图/光机区渲染;
  8. python package_release.py <版本> —— 不报"引用图片缺失"即栏位完整性 OK。