chore: 文件夹更名 HDMI-Tool -> DLPilot(路径与文档此前已对齐)
This commit is contained in:
@@ -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()
|
||||
Reference in New Issue
Block a user