chore: 文件夹更名 HDMI-Tool -> DLPilot(路径与文档此前已对齐)

This commit is contained in:
2026-09-14 15:25:05 +08:00
parent df6a5f4343
commit 31fcd93607
56 changed files with 0 additions and 0 deletions
+198
View File
@@ -0,0 +1,198 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
HDMI EDID 查看器 + 测试图案投屏 + 光机控制 — 入口与命令行模式
================================================
配合 LT8619C(HDMI 转 RGB888)→ DLPC3421 转接板调试使用。
模块划分(重构自原单文件):
app_gui.py CustomTkinter 主界面(双栏仪表盘)
display_win.py Win32 显示器枚举/分辨率/拓扑
edid_parse.py EDID 1.4 / CEA-861 解析
patterns.py 测试图案全屏投屏窗口
config.py 运行目录与 pattern_slots.json 配置
dlpc_rs485.py 光机 RS485 协议与串口链路
依赖: CLI/EDID 纯标准库; GUI 需 customtkinter(py -m pip install customtkinter);
可选 pyserial(光机控制)与 Pillow(自定义图片 JPG/BMP/WebP)。
命令行模式(自动化测试用):
python hdmi_gui.py 启动 GUI
python hdmi_gui.py --dump 控制台输出 EDID 解算结果
python hdmi_gui.py --edid-fields 控制台输出 EDID 逐字节字段解析表
python hdmi_gui.py --test-pattern Checker --seconds 3
直接出图案, 3 秒自动退出
python hdmi_gui.py --test-pattern Blue --area 800x600
指定测试卡区域(full=铺满所选屏)
python hdmi_gui.py --port COM9 --dlpc-cmd M730S0 --dlpc-cmd M999
RS485 逐条发光机命令并打印译码响应
python hdmi_gui.py --seconds 5 GUI 5 秒自动关闭(冒烟测试)
"""
import argparse
import os
import sys
import time
import dlpc_rs485
import display_win
import edid_parse
import patterns
from config import (AREA_OPTIONS, BASE_DIR, DEFAULT_AREA, SLOT_COUNT, load_slots)
from display_win import ensure_display_extend, get_displays
from edid_parse import edid_field_text, edid_info_lines, hex_dump, parse_edid
from patterns import PATTERN_NAMES, show_pattern, show_pattern_image
def run_gui(auto_close=0):
"""启动 CustomTkinter 主界面(实现在 app_gui.py)"""
import app_gui
app_gui.run_gui(auto_close)
def run_dump(edid_fields=False):
displays = get_displays()
if not displays:
print('未找到活动显示器')
return 1
for i, d in enumerate(displays, 1):
print()
print(f'== 显示器 {i} {d["device"]} 显卡[{d["adapter"]}] '
f'监视器[{d["mon_name"]}] 当前输出 {d["cur_mode"]}')
if not d['edid']:
print(' EDID 读取失败(注册表无 EDID 数据)')
continue
if edid_fields:
print(edid_field_text(d['edid']))
continue
e = parse_edid(d['edid'])
for k, v in edid_info_lines(e):
print(f' {k:<14}: {v}')
print(' ---- 支持的模式 ----')
for k, t in enumerate(e['dtds']):
src = 'DTD1(首选)' if k == 0 else f'DTD{k + 1}'
print(f" [{src}] {t['hact']}x{t['vact']}@{t['refresh']} "
f"{t['clk_khz'] / 1000:.3f}MHz 总{t['htotal']}x{t['vtotal']}")
for m in e['cea_video']:
print(f' [CEA-VIC] {m}')
for m in e['cea_blocks']:
print(f' [CEA-块] {m}')
for m in e['est']:
print(f' [已建立] {m}')
for m in e['std']:
print(f' [标准] {m}')
print(' ---- EDID 原始数据 ----')
print(hex_dump(d['edid']))
return 0
def run_dlpc(port, baud, cmds):
"""RS485 直连模式: 逐条发送光机命令, 等响应打印译码结果(自动化脚本用)。
返回码: 任一命令非法/超时/响应非 0xF0 成功 -> 1, 全部成功 -> 0。
"""
if not dlpc_rs485.HAS_SERIAL:
print('错误: 未安装 pyserial, 请先执行 py -m pip install pyserial')
return 2
try:
ser = dlpc_rs485.serial.Serial(port, baud, timeout=0.1)
except Exception as e:
print(f'打开串口失败: {port} @ {baud}: {e}')
return 1
print(f'已连接 {port} @ {baud} (RS485 半双工, 单发单收)')
parser = dlpc_rs485.FrameParser()
rc = 0
for cmd in cmds:
try:
frame = dlpc_rs485.build_frame(cmd)
except ValueError as e:
print(f'→ {cmd} 命令非法: {e}')
rc = 1
continue
ser.write(frame)
print(f'→ {cmd} [{frame.hex(" ").upper()}]')
deadline = time.time() + 35.0 # M737 定时投光中延迟应答(定时上限 32.7s)
got = None
while got is None and time.time() < deadline:
data = ser.read(256)
if data:
got = next(iter(parser.feed(data)), None)
if got is None:
print('← (35s 超时无响应)')
rc = 1
else:
print(f'← {dlpc_rs485.describe(got)} [{got.raw.hex(" ").upper()}]')
if got.type_code != 0xF0:
rc = 1
ser.close()
return rc
def main():
ap = argparse.ArgumentParser(description='HDMI EDID 查看器 + 测试图案 + 光机控制')
ap.add_argument('--dump', action='store_true', help='控制台输出 EDID 解算结果, 不开 GUI')
ap.add_argument('--edid-fields', action='store_true',
help='控制台输出 EDID 逐字节字段解析表(配合 --dump 风格, 不开 GUI); exe 版不支持')
ap.add_argument('--test-pattern',
choices=list(PATTERN_NAMES) + [f'Custom{i + 1}' for i in range(SLOT_COUNT)],
default=None, help='直接全屏出测试图案(不开主窗口; CustomN 为自定义栏位)')
ap.add_argument('--area', choices=list(AREA_OPTIONS), default=DEFAULT_AREA,
help='测试卡区域: WxH 黑底居中 或 full 铺满所选显示器(默认 640x360)')
ap.add_argument('--monitor', type=int, default=1, help='--test-pattern 用的显示器序号(默认 1)')
ap.add_argument('--seconds', type=int, default=0, help='自动退出秒数(GUI/图案冒烟测试)')
ap.add_argument('--port', default=None, help='光机 RS485 串口(如 COM9), 配合 --dlpc-cmd')
ap.add_argument('--baud', type=int, default=dlpc_rs485.DEFAULT_BAUD,
help='光机串口波特率(默认 115200)')
ap.add_argument('--dlpc-cmd', action='append', default=None, dest='dlpc_cmds',
metavar='CMD',
help='RS485 发送光机命令(可多次), 如 --dlpc-cmd M730S0 --dlpc-cmd M999')
args = ap.parse_args()
# LT8619C 板卡热插拔后可能停留在"仅电脑屏幕"拓扑: 仅当检测到转接板
# (EDID 厂商 XLS)接入但未进桌面时才强制扩展, 其余拓扑不动(失败静默)
ensure_display_extend()
if args.dump or args.edid_fields:
if getattr(sys, 'frozen', False):
# --noconsole 打包的 exe 没有控制台可写, 引导用户用源码方式
import tkinter as tk
from tkinter import messagebox
r = tk.Tk()
r.withdraw()
messagebox.showinfo('不支持', 'exe 版本无控制台输出。\n请用源码方式运行:\n'
'python hdmi_gui.py --edid-fields')
sys.exit(1)
sys.exit(run_dump(edid_fields=args.edid_fields))
if args.dlpc_cmds:
if not args.port:
print('错误: --dlpc-cmd 需要配合 --port 指定串口(如 --port COM9)')
sys.exit(2)
sys.exit(run_dlpc(args.port, args.baud, args.dlpc_cmds))
if args.test_pattern:
displays = get_displays()
if not displays:
print('未找到活动显示器')
sys.exit(1)
d = displays[min(args.monitor, len(displays)) - 1]
if not d['rect']:
print('无法获取显示器矩形')
sys.exit(1)
import tkinter as tk
root = tk.Tk()
root.withdraw()
if args.test_pattern.startswith('Custom'):
slots, _ = load_slots()
slot = slots[int(args.test_pattern[6:]) - 1]
if not slot['file']:
print(f"错误: {args.test_pattern}({slot['name']})未配置图片")
sys.exit(1)
show_pattern_image(root, d['rect'], os.path.join(BASE_DIR, slot['file']),
args.seconds, area=args.area)
else:
show_pattern(root, d['rect'], args.test_pattern, args.seconds, area=args.area)
sys.exit(0)
run_gui(args.seconds)
if __name__ == '__main__':
main()