Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6d1e2c6f8d | ||
|
|
a0d64c322a | ||
|
|
a666ed7c43 | ||
|
|
1f68665860 | ||
|
|
668a7e1ca5 | ||
|
|
bf9d70722c | ||
|
|
5d06e69a41 | ||
|
|
a3430d15e0 | ||
|
|
419c902a38 | ||
|
|
b1bb320b50 | ||
|
|
2d1d8f83b1 | ||
|
|
d991d57447 | ||
|
|
43d78de045 | ||
|
|
739fdeedf3 | ||
|
|
b7eda91ca7 | ||
|
|
763d80534c | ||
|
|
31fcd93607 | ||
|
|
df6a5f4343 |
@@ -8,8 +8,10 @@ dist/
|
||||
*.spec
|
||||
|
||||
# 发布 zip: 可由 src/package_release.py 重新生成, 只上传 Gitea Release
|
||||
HDMI-Tool/release/*.zip
|
||||
DLPilot/release/*.zip
|
||||
|
||||
# 源码模式运行时的配置写入(config.BASE_DIR=src/), 与 release/ 下入库的预置栏位区分
|
||||
DLPilot/src/pattern_slots.json
|
||||
|
||||
# ZCode 客户端本地会话数据(计划留档等), 非项目配置
|
||||
.zcode/
|
||||
|
||||
@@ -101,11 +101,23 @@ DLPilot/
|
||||
**同时是转接板光机 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)语义、
|
||||
- 光机 RS485 命令集(M730/M731/M732/M201/M202/M737/M999/M888/M7/M8/M1/**M303/M304**)语义、
|
||||
帧格式 `D5 01 LEN <ASCII> CRC`(累加和校验)、错误码与**就绪门禁**
|
||||
(DLPC 未就绪时 M730/M731/M732 被拒回 0xFF)以上游 `protocols.md` 为唯一权威,
|
||||
本仓不维护协议文档副本;注意 **M7S0 是"开启"TEC 自动控温**(方向反直觉)、
|
||||
M737 在定时投光中延迟应答(CLI 等待上限 35s)。
|
||||
- **M303/M304 载荷是二进制例外**(SQ52205 电源实测):成功载荷 2 字节大端原始值,
|
||||
**不是 ASCII**——Frame.payload 解出来是乱码,必须走 `payload_bytes()` 取原始字节;
|
||||
M303=u16 mV(12V 轨窗口 11400~12600),M304=s16 补码 mA(正=正向,固件原样上总线,
|
||||
上位机按有符号解析,FF 06=−250)。封装在 `payload_u16_be/payload_s16_be/format_power`,
|
||||
回归向量在 selftest_rs485.py(真机抓取帧)。
|
||||
- **电源自检序列**(M301 协议→M302 挡位最大电流→M303 实测母线电压,帧驱动状态机
|
||||
`_pd_on_frame/_pd_seq`,超时 3s/步):FPDO 判定 = 电压就近挡(标准 PD 固定挡
|
||||
5/9/12/15/20/28V,±5% 容差)+ M302 挡位电流;**12V 且 ≥3A 绿底,挡位不符红底,
|
||||
无匹配挡 ERR**。应答按 TYPE=0xF0 判成功;
|
||||
CH224Q 电压配置寄存器(0x0A)只写不可读,挡位只能实测判定。
|
||||
- **功率检测** = M303+M304 → 读数框 电压V / 电流mA;**功率值暂不计算**
|
||||
(用户要求:后续固件会提供功率命令直接返回,届时填充),显示占位"功率: —"。
|
||||
|
||||
## 5. 构建 / 发布流水线(全部在 src/ 下执行)
|
||||
|
||||
@@ -52,16 +52,17 @@ DLPilot/
|
||||
```
|
||||
左列(HDMI 源端) 右列(光机控制 RS485)
|
||||
┌ 显示器选择区域 [刷新] ┐ ┌ 串口选择 + 波特率/8-N-1 参数/连接 ┐
|
||||
├ 支持的模式: 分辨率[应用][恢复] + 明细 ┐ ├ 串口数据 — 收发日志(译码+hex) ┤
|
||||
├ EDID 信息(表格) ┘ ├ 光机命令 — 快捷按钮+TEC │
|
||||
├ 测试图案 / 纯色 / 自定义 / 测试卡区域 │ 预设下拉 / 自定义 / 温度读取 │
|
||||
├ EDID 原始数据 [逐字节解析] ┘ └ 快捷滑条 — 光强/定时/TEC温度 ┘
|
||||
├ 支持的模式: 分辨率[应用][恢复] + 明细 ┐ ├ 电源检查 — PD自检/FPDO挡位/功率读数 ┤
|
||||
├ EDID 信息 ┘ ├ 串口数据 — 收发日志(译码+hex) ┤
|
||||
├ 测试图案 / 纯色 / 校准图案 / 测试卡区域 │ ├ 光机命令 — 快捷按钮+TEC │
|
||||
├ EDID 原始数据 [逐字节解析] ┘ │ 预设下拉 / 温度电流读取 / 自定义 │
|
||||
└─────────────────────────────┘ └ 快捷滑条 — 光强/定时/TEC温度 ┘
|
||||
```
|
||||
|
||||
1. 左上显示器下拉选择目标屏(多屏时注意别选成主屏),"刷新"重新枚举;
|
||||
2. **切分辨率**:"支持的模式"卡片里选模式 → "应用";测完点"恢复";
|
||||
3. **投测试图**:"测试图案"卡片先选**测试卡区域**(640x360 / 800x600 / full)→
|
||||
点图案按钮全屏投放(新图案自动替换旧图案),按 **ESC** 或点"停止投屏"退出;
|
||||
点图案按钮全屏投放(新图案自动替换旧图案),按 **ESC** 或点"停止测试图像"退出;
|
||||
R/G/B 纯色按钮(55/AA/FF,按通道着色)
|
||||
用于 RGB 位交换类测试;自定义图片点齿轮按钮配置(图片自动复制到 exe 旁),
|
||||
"校准图案"一排为 3D 光固化测试/校准图预留槽位;
|
||||
@@ -99,6 +100,12 @@ GUI"光机控制"区:选串口 → 连接 → 点快捷按钮或命令下拉/
|
||||
多口描述相同时自动附物理位置消歧。日志区实时显示 `→ 命令` / `← 译码结果 (原始 hex)`,
|
||||
错误自动附释义(如 `err:3(I2C NACK)`)。
|
||||
|
||||
"电源检查"卡:**电源自检**依序发 M301(协议)→M302(挡位最大电流)→M303(实测母线电压),
|
||||
按实测电压就近判定 PD 固定挡(5/9/12/15/20/28V,容差 ±5%):**12V 挡且 ≥3A 绿底
|
||||
"FPDO: 12V@3A"**;挡位不符(如 9V 挡)**红底**显示实际挡位;不落在任何标准挡显示
|
||||
**ERR**(CH224Q 电压配置寄存器只写不可读,挡位只能靠实测判定)。**功率检测**依序发
|
||||
M303+M304,读数框显示 电压(V) / 电流(mA);功率暂留空(后续固件命令直接返回)。
|
||||
|
||||
| 命令 | 功能 | 响应示例 |
|
||||
|---|---|---|
|
||||
| `M730S0` / `M730S1` | 开 / 关光机 | `ok` |
|
||||
@@ -106,6 +113,10 @@ GUI"光机控制"区:选串口 → 连接 → 点快捷按钮或命令下拉/
|
||||
| `M731S100` | 设光强 54-800(RGB 同值,写后读回复核) | `ok` / `err:R..G..B..` |
|
||||
| `M732` | 查三路电流设置值(0-65535) | `okR110G110B110` |
|
||||
| `M201` / `M202` | 查 DMD / LAMP 温度(0.1°C) | `okTL:246` = 24.6°C |
|
||||
| `M301` | 查电源协议(CH224Q 握手结果) | ASCII:`PD` / `QC3.0` / `EPR` … |
|
||||
| `M302` | 查 PD 挡位最大电流(u16 大端 **mA**,50mA/LSB 固件已换算) | 0x0BB8 = 3000 mA = 3 A |
|
||||
| `M303` | 查母线电压(SQ52205 实测,**2 字节大端二进制载荷**) | 0x2EF4 = 12020 mV = 12.02 V(12V 轨窗口 11400~12600) |
|
||||
| `M304` | 查负载电流(SQ52205 实测,**2 字节大端二进制载荷**) | s16 补码 mA:0x0034=+52;FF 06=−250(正=正向) |
|
||||
| `M999` | 查固件版本 | `okv20.0.0.1` |
|
||||
| `M737` | 查投光是否结束(投光中延迟应答) | `ok` |
|
||||
| `M7S0` / `M7S1` | TEC 自动控温开 / 关(注意 S0=开) | `ok` |
|
||||
|
Before Width: | Height: | Size: 7.2 KiB After Width: | Height: | Size: 7.2 KiB |
|
Before Width: | Height: | Size: 51 KiB After Width: | Height: | Size: 51 KiB |
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 2.6 KiB |
|
Before Width: | Height: | Size: 7.9 KiB After Width: | Height: | Size: 7.9 KiB |
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 2.4 KiB |
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 2.7 KiB |
@@ -52,16 +52,17 @@ DLPilot/
|
||||
```
|
||||
左列(HDMI 源端) 右列(光机控制 RS485)
|
||||
┌ 显示器选择区域 [刷新] ┐ ┌ 串口选择 + 波特率/8-N-1 参数/连接 ┐
|
||||
├ 支持的模式: 分辨率[应用][恢复] + 明细 ┐ ├ 串口数据 — 收发日志(译码+hex) ┤
|
||||
├ EDID 信息(表格) ┘ ├ 光机命令 — 快捷按钮+TEC │
|
||||
├ 测试图案 / 纯色 / 自定义 / 测试卡区域 │ 预设下拉 / 自定义 / 温度读取 │
|
||||
├ EDID 原始数据 [逐字节解析] ┘ └ 快捷滑条 — 光强/定时/TEC温度 ┘
|
||||
├ 支持的模式: 分辨率[应用][恢复] + 明细 ┐ ├ 电源检查 — PD自检/FPDO挡位/功率读数 ┤
|
||||
├ EDID 信息 ┘ ├ 串口数据 — 收发日志(译码+hex) ┤
|
||||
├ 测试图案 / 纯色 / 校准图案 / 测试卡区域 │ ├ 光机命令 — 快捷按钮+TEC │
|
||||
├ EDID 原始数据 [逐字节解析] ┘ │ 预设下拉 / 温度电流读取 / 自定义 │
|
||||
└─────────────────────────────┘ └ 快捷滑条 — 光强/定时/TEC温度 ┘
|
||||
```
|
||||
|
||||
1. 左上显示器下拉选择目标屏(多屏时注意别选成主屏),"刷新"重新枚举;
|
||||
2. **切分辨率**:"支持的模式"卡片里选模式 → "应用";测完点"恢复";
|
||||
3. **投测试图**:"测试图案"卡片先选**测试卡区域**(640x360 / 800x600 / full)→
|
||||
点图案按钮全屏投放(新图案自动替换旧图案),按 **ESC** 或点"停止投屏"退出;
|
||||
点图案按钮全屏投放(新图案自动替换旧图案),按 **ESC** 或点"停止测试图像"退出;
|
||||
R/G/B 纯色按钮(55/AA/FF,按通道着色)
|
||||
用于 RGB 位交换类测试;自定义图片点齿轮按钮配置(图片自动复制到 exe 旁),
|
||||
"校准图案"一排为 3D 光固化测试/校准图预留槽位;
|
||||
@@ -99,6 +100,12 @@ GUI"光机控制"区:选串口 → 连接 → 点快捷按钮或命令下拉/
|
||||
多口描述相同时自动附物理位置消歧。日志区实时显示 `→ 命令` / `← 译码结果 (原始 hex)`,
|
||||
错误自动附释义(如 `err:3(I2C NACK)`)。
|
||||
|
||||
"电源检查"卡:**电源自检**依序发 M301(协议)→M302(挡位最大电流)→M303(实测母线电压),
|
||||
按实测电压就近判定 PD 固定挡(5/9/12/15/20/28V,容差 ±5%):**12V 挡且 ≥3A 绿底
|
||||
"FPDO: 12V@3A"**;挡位不符(如 9V 挡)**红底**显示实际挡位;不落在任何标准挡显示
|
||||
**ERR**(CH224Q 电压配置寄存器只写不可读,挡位只能靠实测判定)。**功率检测**依序发
|
||||
M303+M304,读数框显示 电压(V) / 电流(mA);功率暂留空(后续固件命令直接返回)。
|
||||
|
||||
| 命令 | 功能 | 响应示例 |
|
||||
|---|---|---|
|
||||
| `M730S0` / `M730S1` | 开 / 关光机 | `ok` |
|
||||
@@ -106,6 +113,10 @@ GUI"光机控制"区:选串口 → 连接 → 点快捷按钮或命令下拉/
|
||||
| `M731S100` | 设光强 54-800(RGB 同值,写后读回复核) | `ok` / `err:R..G..B..` |
|
||||
| `M732` | 查三路电流设置值(0-65535) | `okR110G110B110` |
|
||||
| `M201` / `M202` | 查 DMD / LAMP 温度(0.1°C) | `okTL:246` = 24.6°C |
|
||||
| `M301` | 查电源协议(CH224Q 握手结果) | ASCII:`PD` / `QC3.0` / `EPR` … |
|
||||
| `M302` | 查 PD 挡位最大电流(u16 大端 **mA**,50mA/LSB 固件已换算) | 0x0BB8 = 3000 mA = 3 A |
|
||||
| `M303` | 查母线电压(SQ52205 实测,**2 字节大端二进制载荷**) | 0x2EF4 = 12020 mV = 12.02 V(12V 轨窗口 11400~12600) |
|
||||
| `M304` | 查负载电流(SQ52205 实测,**2 字节大端二进制载荷**) | s16 补码 mA:0x0034=+52;FF 06=−250(正=正向) |
|
||||
| `M999` | 查固件版本 | `okv20.0.0.1` |
|
||||
| `M737` | 查投光是否结束(投光中延迟应答) | `ok` |
|
||||
| `M7S0` / `M7S1` | TEC 自动控温开 / 关(注意 S0=开) | `ok` |
|
||||
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 2.6 KiB |
|
Before Width: | Height: | Size: 7.9 KiB After Width: | Height: | Size: 7.9 KiB |
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 2.4 KiB |
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
@@ -0,0 +1,52 @@
|
||||
{
|
||||
"area": "640x360",
|
||||
"slots": [
|
||||
{
|
||||
"name": "西门子星",
|
||||
"file": "bw_star.png"
|
||||
},
|
||||
{
|
||||
"name": "BW_ZONE",
|
||||
"file": "bw_zoneplate.png"
|
||||
},
|
||||
{
|
||||
"name": "BW_TEST",
|
||||
"file": "bw_testcard.png"
|
||||
},
|
||||
{
|
||||
"name": "自定义4",
|
||||
"file": null
|
||||
},
|
||||
{
|
||||
"name": "自定义5",
|
||||
"file": null
|
||||
},
|
||||
{
|
||||
"name": "自定义6",
|
||||
"file": null
|
||||
},
|
||||
{
|
||||
"name": "自定义7",
|
||||
"file": null
|
||||
},
|
||||
{
|
||||
"name": "自定义8",
|
||||
"file": null
|
||||
},
|
||||
{
|
||||
"name": "自定义9",
|
||||
"file": null
|
||||
},
|
||||
{
|
||||
"name": "自定义10",
|
||||
"file": null
|
||||
}
|
||||
],
|
||||
"serial": {
|
||||
"port": "COM15",
|
||||
"baud": 115200,
|
||||
"bytesize": 8,
|
||||
"parity": "N",
|
||||
"stopbits": 1
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 2.7 KiB |
@@ -33,8 +33,14 @@ MONO = ('Consolas', 12)
|
||||
BOLD = ('Microsoft YaHei UI', 13, 'bold')
|
||||
TITLE_FONT = ('Microsoft YaHei UI', 18, 'bold')
|
||||
|
||||
# 电源自检状态位配色: 灰=未检测, 绿=正常回包, 红=错误/超时
|
||||
CHIP_IDLE = ('gray78', 'gray34')
|
||||
CHIP_IDLE_TEXT = ('#333333', '#E8E8E8')
|
||||
CHIP_OK = '#2FA574'
|
||||
CHIP_ERR = '#D9534F'
|
||||
|
||||
APP_NAME = 'DLPilot'
|
||||
APP_VERSION = '0.3.1'
|
||||
APP_VERSION = '0.3.5'
|
||||
REPO_URL = 'https://gitea.hulk.wang/hulk/LT8619C_Debug_Tools'
|
||||
|
||||
|
||||
@@ -57,23 +63,44 @@ def _apply_dropdown_theme():
|
||||
ctk.ThemeManager.theme['DropdownMenu']['text_color'] = ('#1A1A1A', '#DADADA')
|
||||
|
||||
|
||||
def _ensure_single_instance():
|
||||
"""单实例锁(Windows 命名互斥体): 已有实例在运行时返回 False。
|
||||
|
||||
双击 exe 开多个实例时, 只有先启动的能连串口, 后续实例连接报"拒绝访问",
|
||||
且用户往往只看到后开的窗口 —— 表现为"连不上/断不开"。互斥体句柄随
|
||||
进程存活, 进程退出自动释放。"""
|
||||
ERROR_ALREADY_EXISTS = 183
|
||||
ctypes.windll.kernel32.CreateMutexW(None, False, 'hulk.DLPilot.single')
|
||||
return ctypes.windll.kernel32.GetLastError() != ERROR_ALREADY_EXISTS
|
||||
|
||||
|
||||
def run_gui(auto_close=0):
|
||||
"""入口: 配置外观并启动主窗口"""
|
||||
ctk.set_appearance_mode('System')
|
||||
_apply_dropdown_theme()
|
||||
if not _ensure_single_instance():
|
||||
from tkinter import messagebox
|
||||
import tkinter as tk
|
||||
r = tk.Tk()
|
||||
r.withdraw()
|
||||
messagebox.showinfo('DLPilot 已在运行',
|
||||
'检测到另一个 DLPilot 实例正在运行。\n'
|
||||
'请使用已打开的窗口(串口同时只能被一个实例占用)。')
|
||||
return
|
||||
App(auto_close).mainloop()
|
||||
|
||||
|
||||
class App(ctk.CTk):
|
||||
def __init__(self, auto_close=0):
|
||||
super().__init__()
|
||||
self.title('DLPilot — 3D 光固化光机调试台 (EDID · 测试图案 · RS485 光机控制)')
|
||||
self.title(f'DLPilot v{APP_VERSION} — 3D 光固化光机调试台 '
|
||||
f'(EDID · 测试图案 · RS485 光机控制)')
|
||||
try: # 任务栏独立分组
|
||||
ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID('hulk.DLPilot')
|
||||
except Exception:
|
||||
pass
|
||||
self.geometry('1440x920')
|
||||
self.minsize(1180, 800)
|
||||
self.geometry('1440x1010') # 右列新增电源检查卡, 窗口加高
|
||||
self.minsize(1180, 860)
|
||||
ic = get_icon_path()
|
||||
if ic:
|
||||
try:
|
||||
@@ -88,10 +115,17 @@ class App(ctk.CTk):
|
||||
self._orig_modes = {} # device -> 切改前的原始 DEVMODEW
|
||||
# 光机控制链路: 回调发生在串口线程, 经队列投递回 GUI 主线程轮询
|
||||
self._dlpc_log_q = queue.Queue()
|
||||
self.dlpc = dlpc_rs485.DLPC485(self._dlpc_on_event, self._dlpc_on_status)
|
||||
self.dlpc = dlpc_rs485.DLPC485(self._dlpc_on_event, self._dlpc_on_status,
|
||||
on_frame=self._dlpc_on_frame)
|
||||
self._dlpc_connected = False
|
||||
self._poll_job = None
|
||||
self._tl_order = [] # 温度查询应答顺序: 'DMD'/'LAMP' 先到先得
|
||||
self._pd_seq = [] # 电源自检待执行步骤: [(命令, kind), ...]
|
||||
self._pd_values = {} # 自检采集值: {'ma': 挡位电流, 'mv': 实测电压}
|
||||
self._pd_timer = None # 自检单步超时定时器
|
||||
self._pwr_seq = [] # 功率检测待执行步骤
|
||||
self._pwr_values = {} # 功率检测采集值: {'mv', 'ma'}
|
||||
self._pwr_timer = None
|
||||
self._build()
|
||||
self._load_displays()
|
||||
# 主窗口按 ESC 也能关闭所有投屏(投屏窗口失焦时的兜底出口)
|
||||
@@ -204,14 +238,17 @@ class App(ctk.CTk):
|
||||
self.txt_modes.configure(state='disabled')
|
||||
|
||||
# -- EDID 信息 --
|
||||
# CTkTextbox 而非 CTkScrollableFrame+逐行 Label: 后者 40+ 个画布控件,
|
||||
# 窗口最大化时逐个重绘是卡顿主因; 文本框整体重排, 开销低一个量级
|
||||
card, c = self._card(col, 'EDID 信息')
|
||||
card.grid(row=2, column=0, sticky='nsew', pady=(10, 0))
|
||||
self.edid_scroll = ctk.CTkScrollableFrame(c, height=150)
|
||||
self.edid_scroll.pack(fill='both', expand=True)
|
||||
self.txt_edid = ctk.CTkTextbox(c, font=MONO, wrap='none')
|
||||
self.txt_edid.pack(fill='both', expand=True)
|
||||
self.txt_edid.configure(state='disabled')
|
||||
|
||||
# -- 测试图案 --
|
||||
card, c = self._card(col,'测试图案 — 黑底 1:1 居中, 投屏按 ESC 或"停止投屏"退出',
|
||||
button='停止投屏', command=patterns.close_all)
|
||||
card, c = self._card(col,'测试图案 — 黑底 1:1 居中, 投屏按 ESC 或"停止测试图像"退出',
|
||||
button='停止测试图像', command=patterns.close_all)
|
||||
card.grid(row=3, column=0, sticky='ew', pady=(10, 0))
|
||||
row1 = ctk.CTkFrame(c, fg_color='transparent')
|
||||
row1.pack(fill='x')
|
||||
@@ -318,6 +355,39 @@ class App(ctk.CTk):
|
||||
self._dlpc_log('帧格式: D5 01 LEN <命令> CRC / B5 TYPE LEN <payload> CRC, '
|
||||
'累加和校验; 全部命令见 README"光机控制"')
|
||||
|
||||
# -- 电源检查 --
|
||||
# 网格对齐两行: col2 为读数框列(weight=1) -> FPDO 色框与功率黑框
|
||||
# 起止位置完全一致(等长), 且随窗口拉伸自适应
|
||||
card, c = self._card(col, '电源检查 — PD 自检 / 功率读数')
|
||||
card.grid(row=2, column=0, sticky='ew', pady=(10, 0))
|
||||
pwr_grid = ctk.CTkFrame(c, fg_color='transparent')
|
||||
pwr_grid.pack(fill='x')
|
||||
pwr_grid.grid_columnconfigure(2, weight=1)
|
||||
ctk.CTkButton(pwr_grid, text='电源自检', width=90,
|
||||
command=self._pd_selftest).grid(row=0, column=0, sticky='w')
|
||||
proto_cell = ctk.CTkFrame(pwr_grid, fg_color='transparent')
|
||||
proto_cell.grid(row=0, column=1, sticky='w', padx=(10, 0))
|
||||
ctk.CTkLabel(proto_cell, text='协议:').pack(side='left', padx=(0, 4))
|
||||
self.chip_pd = ctk.CTkLabel(proto_cell, text='PD', width=60, height=26,
|
||||
corner_radius=6, fg_color=CHIP_IDLE,
|
||||
text_color=CHIP_IDLE_TEXT)
|
||||
self.chip_pd.pack(side='left')
|
||||
self.var_fpdo = tk.StringVar(value='FPDO: —')
|
||||
self.chip_fpdo = ctk.CTkLabel(pwr_grid, textvariable=self.var_fpdo,
|
||||
height=26, corner_radius=6,
|
||||
fg_color=CHIP_IDLE,
|
||||
text_color=CHIP_IDLE_TEXT, anchor='w', padx=8)
|
||||
self.chip_fpdo.grid(row=0, column=2, sticky='ew', padx=(12, 0))
|
||||
ctk.CTkButton(pwr_grid, text='功率检测', width=90,
|
||||
command=self._pwr_query).grid(row=1, column=0,
|
||||
sticky='w', pady=(6, 0))
|
||||
self.var_pwr = tk.StringVar(value='电压: — 电流: — 功率: —')
|
||||
self.box_pwr = ctk.CTkLabel(pwr_grid, textvariable=self.var_pwr,
|
||||
height=26, corner_radius=6,
|
||||
fg_color=('#F2F2F2', '#181818'),
|
||||
anchor='w', padx=8)
|
||||
self.box_pwr.grid(row=1, column=2, sticky='ew', padx=(8, 0), pady=(6, 0))
|
||||
|
||||
# -- 光机命令 --
|
||||
# 标题行右侧: 电流实时读数 + 查电流按钮(okR..G..B.. 应答解析)
|
||||
cur_frame = ctk.CTkFrame(self, fg_color='transparent')
|
||||
@@ -328,7 +398,7 @@ class App(ctk.CTk):
|
||||
command=self._query_current).pack(side='left', padx=(6, 0))
|
||||
card, c = self._card(col,'光机命令 — 预设 / 自定义 / TEC',
|
||||
right_widget=cur_frame)
|
||||
card.grid(row=2, column=0, sticky='ew', pady=(10, 0))
|
||||
card.grid(row=3, column=0, sticky='ew', pady=(10, 0))
|
||||
row1 = ctk.CTkFrame(c, fg_color='transparent')
|
||||
row1.pack(fill='x')
|
||||
for name, cmd in (('开光机', 'M730S0'), ('关光机', 'M730S1'),
|
||||
@@ -385,7 +455,7 @@ class App(ctk.CTk):
|
||||
|
||||
# -- 快捷滑条 --
|
||||
card, c = self._card(col,'快捷滑条 — 光强 / 定时投光 / TEC 温度')
|
||||
card.grid(row=3, column=0, sticky='ew', pady=(10, 0))
|
||||
card.grid(row=4, column=0, sticky='ew', pady=(10, 0))
|
||||
rowa = ctk.CTkFrame(c, fg_color='transparent')
|
||||
rowa.pack(fill='x')
|
||||
ctk.CTkLabel(rowa, text='光强(54-800):').pack(side='left')
|
||||
@@ -502,16 +572,13 @@ class App(ctk.CTk):
|
||||
self._set_text(self.txt_hex, edid_parse.hex_dump(d['edid']))
|
||||
|
||||
def _clear_edid_rows(self):
|
||||
for w in self.edid_scroll.winfo_children():
|
||||
w.destroy()
|
||||
self._set_text(self.txt_edid, '')
|
||||
|
||||
def _add_edid_row(self, k, v):
|
||||
row = ctk.CTkFrame(self.edid_scroll, fg_color='transparent')
|
||||
row.pack(fill='x', pady=1)
|
||||
ctk.CTkLabel(row, text=k, width=132, anchor='w',
|
||||
text_color='gray60').pack(side='left')
|
||||
ctk.CTkLabel(row, text=v, anchor='w').pack(side='left',
|
||||
fill='x', expand=True)
|
||||
self.txt_edid.configure(state='normal')
|
||||
self.txt_edid.insert('end', f'{k:<16}{v}\n')
|
||||
self.txt_edid.see('end')
|
||||
self.txt_edid.configure(state='disabled')
|
||||
|
||||
def _apply_mode(self):
|
||||
d = self._cur_display()
|
||||
@@ -679,6 +746,13 @@ class App(ctk.CTk):
|
||||
# ============================================================
|
||||
# 光机控制(RS485)
|
||||
# ============================================================
|
||||
def _dlpc_on_frame(self, fr):
|
||||
"""结构化应答帧(串口线程): 电源自检/功率检测序列进行中时路由给状态机"""
|
||||
if self._pd_seq:
|
||||
self._dlpc_log_q.put(('pdframe', fr))
|
||||
elif self._pwr_seq:
|
||||
self._dlpc_log_q.put(('pwrframe', fr))
|
||||
|
||||
def _dlpc_on_event(self, line):
|
||||
# 温度查询应答: okTL:<0.1°C>, 依发送顺序归入 DMD/LAMP 实时显示框
|
||||
m = re.search(r'← okTL:(-?\d+)', line)
|
||||
@@ -724,6 +798,8 @@ class App(ctk.CTk):
|
||||
else:
|
||||
color = 'gray60'
|
||||
self.lbl_dlpc_st.configure(text=text, text_color=color)
|
||||
if text.startswith(('已断开', '串口丢失')):
|
||||
self._serial_reset_state()
|
||||
elif kind == 'temp':
|
||||
if text.startswith('DMD'):
|
||||
self.var_dmd.set(text)
|
||||
@@ -731,6 +807,11 @@ class App(ctk.CTk):
|
||||
self.var_lamp.set(text)
|
||||
elif kind == 'current':
|
||||
self.var_current.set(text)
|
||||
elif kind == 'pdframe':
|
||||
self._pd_step_done(text.type_code == 0xF0, text)
|
||||
elif kind == 'pwrframe':
|
||||
self._pwr_step_done(text)
|
||||
else:
|
||||
self._dlpc_log(text)
|
||||
except queue.Empty:
|
||||
pass
|
||||
@@ -777,6 +858,7 @@ class App(ctk.CTk):
|
||||
def _dlpc_toggle(self):
|
||||
if self.dlpc.is_connected():
|
||||
self.dlpc.close()
|
||||
self._serial_reset_state() # 断开时清序列/定时器/应答归位, 防旧账错配
|
||||
return
|
||||
port = self._dlpc_selected_port()
|
||||
if not port:
|
||||
@@ -795,10 +877,28 @@ class App(ctk.CTk):
|
||||
config.save_slots(self.slots, self.area_var.get()) # 记住本次串口
|
||||
self.dlpc.open(port, baud, bytesize=bytesize, parity=parity, stopbits=stopbits)
|
||||
|
||||
def _serial_reset_state(self):
|
||||
"""断开/失连时清理进行中的序列与应答归位队列(不碰串口线程本身)"""
|
||||
self._pd_seq.clear()
|
||||
self._pwr_seq.clear()
|
||||
for t in (self._pd_timer, self._pwr_timer):
|
||||
if t:
|
||||
try:
|
||||
self.after_cancel(t)
|
||||
except Exception:
|
||||
pass
|
||||
self._pd_timer = self._pwr_timer = None
|
||||
self._tl_order.clear()
|
||||
|
||||
def _dlpc_send(self, cmd):
|
||||
if not self.dlpc.is_connected():
|
||||
self.lbl_dlpc_st.configure(text='未连接串口', text_color='#E07A3F')
|
||||
return
|
||||
if self._pd_seq or self._pwr_seq:
|
||||
# 序列进行中: 手动命令的应答帧会被序列错认领(它不区分应答属于哪条
|
||||
# 命令, 来一帧消费一步), 值会错位 —— 拒绝并提示, 序列结束再发
|
||||
self._dlpc_log(f'序列进行中, 命令已忽略: {cmd}')
|
||||
return
|
||||
self.dlpc.send(cmd)
|
||||
|
||||
def _dlpc_send_preset(self):
|
||||
@@ -813,6 +913,134 @@ class App(ctk.CTk):
|
||||
if cmd:
|
||||
self._dlpc_send(cmd)
|
||||
|
||||
# -- 电源检查: 自检序列 + 功率读数 --
|
||||
|
||||
def _pd_selftest(self):
|
||||
"""电源自检: 依序 M301(协议) → M302(挡位最大电流) → M303(实测母线电压),
|
||||
应答帧驱动序列前进(成功=TYPE 0xF0), 结束后按 _pd_fpdo_verdict 判定 FPDO"""
|
||||
if not self.dlpc.is_connected():
|
||||
self.lbl_dlpc_st.configure(text='未连接串口', text_color='#E07A3F')
|
||||
return
|
||||
if self._pd_seq:
|
||||
return # 进行中, 忽略重复触发
|
||||
self._pd_seq = [('M301', 'pd'), ('M302', 'fpdo_ma'), ('M303', 'meas_mv')]
|
||||
self._pd_values = {}
|
||||
self._pd_chip_reset()
|
||||
self._dlpc_log('电源自检: 开始')
|
||||
self._pd_step_next()
|
||||
|
||||
def _pd_chip_reset(self):
|
||||
self.chip_pd.configure(fg_color=CHIP_IDLE,
|
||||
text_color=CHIP_IDLE_TEXT, text='PD')
|
||||
self.chip_fpdo.configure(fg_color=CHIP_IDLE, text_color=CHIP_IDLE_TEXT)
|
||||
self.var_fpdo.set('FPDO: —')
|
||||
|
||||
def _pd_step_next(self):
|
||||
if not self._pd_seq:
|
||||
self._pd_fpdo_verdict()
|
||||
self._dlpc_log('电源自检: 完成')
|
||||
return
|
||||
self.dlpc.send(self._pd_seq[0][0])
|
||||
self._pd_timer = self.after(3000, lambda: self._pd_step_done(False, None))
|
||||
|
||||
def _pd_step_done(self, ok, fr):
|
||||
if not self._pd_seq:
|
||||
return
|
||||
if self._pd_timer:
|
||||
self.after_cancel(self._pd_timer)
|
||||
self._pd_timer = None
|
||||
cmd, kind = self._pd_seq.pop(0)
|
||||
raw = dlpc_rs485.payload_bytes(fr) if fr is not None else b''
|
||||
if ok and kind == 'pd':
|
||||
proto = raw.decode('ascii', 'replace').strip()
|
||||
good = proto in ('PD', 'EPR')
|
||||
self.chip_pd.configure(fg_color=CHIP_OK if good else CHIP_ERR,
|
||||
text_color='#FFFFFF', text=proto or 'PD')
|
||||
elif ok and kind == 'fpdo_ma' and len(raw) == 2:
|
||||
self._pd_values['ma'] = dlpc_rs485.payload_u16_be(raw)
|
||||
elif ok and kind == 'meas_mv' and len(raw) == 2:
|
||||
self._pd_values['mv'] = dlpc_rs485.payload_u16_be(raw)
|
||||
if ok:
|
||||
self._pd_step_next()
|
||||
else: # 出错即中止
|
||||
self._pd_seq.clear()
|
||||
if kind == 'pd':
|
||||
self.chip_pd.configure(fg_color=CHIP_ERR, text_color='#FFFFFF',
|
||||
text='ERR')
|
||||
elif kind == 'fpdo_ma':
|
||||
self.chip_fpdo.configure(fg_color=CHIP_ERR, text_color='#FFFFFF')
|
||||
self.var_fpdo.set('FPDO: ERR')
|
||||
self._dlpc_log('电源自检: 应答错误/超时, 序列中止')
|
||||
|
||||
def _pd_fpdo_verdict(self):
|
||||
"""FPDO 判定: 电压挡位由 M303 实测母线电压就近判定(标准 PD 固定挡
|
||||
5/9/12/15/20/28V, 容差 ±5%; CH224Q 0x0A 只写不可读, 挡位只能实测判定),
|
||||
电流为 M302 挡位最大电流。默认挡 12V 且 ≥3A -> 绿底;
|
||||
挡位不符(如 9V 挡) -> 红底; 实测电压不落在任何标准挡 -> ERR"""
|
||||
mv = self._pd_values.get('mv')
|
||||
ma = self._pd_values.get('ma')
|
||||
if mv is None or ma is None:
|
||||
self.chip_fpdo.configure(fg_color=CHIP_ERR, text_color='#FFFFFF')
|
||||
self.var_fpdo.set('FPDO: ERR')
|
||||
return
|
||||
gear = next((g for g in (5, 9, 12, 15, 20, 28)
|
||||
if abs(mv - g * 1000) <= g * 50), None) # ±5% = g*50 mV
|
||||
a_txt = f'{ma / 1000:g}'
|
||||
if gear is None:
|
||||
self.chip_fpdo.configure(fg_color=CHIP_ERR, text_color='#FFFFFF')
|
||||
self.var_fpdo.set('FPDO: ERR')
|
||||
elif gear == 12 and ma >= 3000:
|
||||
self.chip_fpdo.configure(fg_color=CHIP_OK, text_color='#FFFFFF')
|
||||
self.var_fpdo.set(f'FPDO: 12V@{a_txt}A')
|
||||
else:
|
||||
self.chip_fpdo.configure(fg_color=CHIP_ERR, text_color='#FFFFFF')
|
||||
self.var_fpdo.set(f'FPDO: {gear}V@{a_txt}A')
|
||||
|
||||
def _pwr_query(self):
|
||||
"""功率检测: M303 实测电压 + M304 实测电流(按有符号); 功率值待固件功率命令"""
|
||||
if not self.dlpc.is_connected():
|
||||
self.lbl_dlpc_st.configure(text='未连接串口', text_color='#E07A3F')
|
||||
return
|
||||
if self._pwr_seq:
|
||||
return
|
||||
self._pwr_seq = [('M303', 'mv'), ('M304', 'ma')]
|
||||
self._pwr_values = {}
|
||||
self._dlpc_log('功率检测: 开始')
|
||||
self._pwr_step_next()
|
||||
|
||||
def _pwr_step_next(self):
|
||||
if not self._pwr_seq:
|
||||
v_mv = self._pwr_values.get('mv', 0)
|
||||
ma = self._pwr_values.get('ma', 0)
|
||||
self._pwr_update(v_mv, ma)
|
||||
self._dlpc_log('功率检测: 完成')
|
||||
return
|
||||
self.dlpc.send(self._pwr_seq[0][0])
|
||||
self._pwr_timer = self.after(3000, lambda: self._pwr_step_done(None))
|
||||
|
||||
def _pwr_step_done(self, fr):
|
||||
if not self._pwr_seq:
|
||||
return
|
||||
if self._pwr_timer:
|
||||
self.after_cancel(self._pwr_timer)
|
||||
self._pwr_timer = None
|
||||
cmd, kind = self._pwr_seq.pop(0)
|
||||
raw = dlpc_rs485.payload_bytes(fr) if fr is not None else b''
|
||||
if fr is None or fr.type_code != 0xF0 or len(raw) != 2:
|
||||
self._pwr_seq.clear()
|
||||
self.var_pwr.set('电压: ERR 电流: — 功率: —')
|
||||
self._dlpc_log('功率检测: 应答错误/超时, 中止')
|
||||
return
|
||||
if kind == 'mv':
|
||||
self._pwr_values['mv'] = dlpc_rs485.payload_u16_be(raw)
|
||||
elif kind == 'ma':
|
||||
self._pwr_values['ma'] = dlpc_rs485.payload_s16_be(raw)
|
||||
self._pwr_step_next()
|
||||
|
||||
def _pwr_update(self, v_mv, ma):
|
||||
"""功率读数框更新: 电压 V / 电流 mA; 功率暂不计算(待固件功率命令返回后填充)"""
|
||||
self.var_pwr.set(f'电压: {v_mv / 1000:.2f} V 电流: {ma} mA 功率: —')
|
||||
|
||||
def _light_entry_apply(self, _evt=None):
|
||||
"""滑条数值输入框回车: 校验并限幅 54-800, 同步回滑条"""
|
||||
try:
|
||||
@@ -878,9 +1106,13 @@ class App(ctk.CTk):
|
||||
self._dlpc_send(f'M8S{v}')
|
||||
|
||||
def destroy(self):
|
||||
try: # 取消待触发的轮询回调, 避免销毁后 Tcl 报错
|
||||
try: # 取消待触发的轮询/自检/功率超时回调, 避免销毁后 Tcl 报错
|
||||
if self._poll_job:
|
||||
self.after_cancel(self._poll_job)
|
||||
if self._pd_timer:
|
||||
self.after_cancel(self._pd_timer)
|
||||
if self._pwr_timer:
|
||||
self.after_cancel(self._pwr_timer)
|
||||
except Exception:
|
||||
pass
|
||||
try: # 两种退出路径(关窗/--seconds 自动关)都收掉串口线程
|
||||
@@ -7,7 +7,9 @@ DLPC3421 光机 RS485 控制链路(板上 GD32E230 作 RS485→I2C 网关)
|
||||
响应语义/错误码/就绪门禁与实测帧示例):
|
||||
物理层 RS485 半双工 115200 8N1(PC 经 USB-RS485 转换器接板, DE 由 MCU 硬件换向)
|
||||
命令帧 D5 01 LEN <ASCII命令> CRC 01=板类型, LEN=命令字节数
|
||||
响应帧 B5 TYPE LEN <payload> CRC payload 为 ASCII(ok / err:OL / okTL:246 ...)
|
||||
响应帧 B5 TYPE LEN <payload> CRC payload 为 ASCII(ok / err:OL / okTL:246 ...),
|
||||
例外: M303/M304 载荷为 2 字节大端二进制
|
||||
(SQ52205 电源实测, 见 POWER_COMMANDS)
|
||||
CRC 帧内第 2 字节(板类型)起累加到最后一个 payload 字节, 取低 8 位
|
||||
帧长 最小 6 字节, 最大 32 字节
|
||||
就绪门禁: DLPC3421 启动完成前 M730/M731/M732 被拒(回 0xFF), 属预期。
|
||||
@@ -81,6 +83,42 @@ PRESETS = (
|
||||
Frame = collections.namedtuple('Frame', 'type_code payload raw')
|
||||
|
||||
|
||||
# --- M303/M304(SQ52205 电源实测值查询): 成功载荷为 2 字节原始二进制(非 ASCII) ---
|
||||
# M303 母线电压: u16 大端, 单位 mV; 12V 轨正常窗口 11400~12600(标称 ±5%)
|
||||
# M304 负载电流: s16 大端补码, 单位 mA; 正=正向, 负=反向(固件原样上总线,
|
||||
# 上位机必须按有符号解析)。例: 载荷 FF 06 = -250 mA
|
||||
# 错误时 TYPE=0xFF, 载荷为错误信息字节串(走通用译码路径)
|
||||
POWER_COMMANDS = ('M303', 'M304')
|
||||
|
||||
|
||||
def payload_bytes(fr):
|
||||
"""响应帧的原始载荷字节(Frame.payload 是 ASCII 解码视图, 二进制载荷请用本函数)"""
|
||||
return fr.raw[3:-1]
|
||||
|
||||
|
||||
def payload_u16_be(raw):
|
||||
return (raw[0] << 8) | raw[1]
|
||||
|
||||
|
||||
def payload_s16_be(raw):
|
||||
v = payload_u16_be(raw)
|
||||
return v - 0x10000 if v & 0x8000 else v
|
||||
|
||||
|
||||
def format_power(cmd, fr):
|
||||
"""M303/M304 成功应答 -> 人读文本; 非 POWER 命令/失败/载荷异常返回 None"""
|
||||
if cmd not in POWER_COMMANDS:
|
||||
return None
|
||||
raw = payload_bytes(fr)
|
||||
if fr.type_code != 0xF0 or len(raw) != 2:
|
||||
return None
|
||||
if cmd == 'M303':
|
||||
mv = payload_u16_be(raw)
|
||||
return f'{mv} mV ({mv / 1000:.2f} V)'
|
||||
ma = payload_s16_be(raw)
|
||||
return f'{ma:+d} mA'
|
||||
|
||||
|
||||
def sum_crc(data):
|
||||
"""帧内第 2 字节(板类型)起累加到末 payload 字节, 取低 8 位(不含包头与 CRC)"""
|
||||
return sum(data) & 0xFF
|
||||
@@ -158,8 +196,8 @@ class DLPC485:
|
||||
回调在后台线程触发, GUI 侧需自行投递回主线程(见 hdmi_gui 的轮询队列)。
|
||||
"""
|
||||
|
||||
def __init__(self, on_event, on_status):
|
||||
self.on_event, self.on_status = on_event, on_status
|
||||
def __init__(self, on_event, on_status, on_frame=None):
|
||||
self.on_event, self.on_status, self.on_frame = on_event, on_status, on_frame
|
||||
self.q = queue.Queue()
|
||||
self._target = None # 想态 (port, baud, bytesize, parity, stopbits)
|
||||
self._open_key = None # 当前已打开串口的参数元组
|
||||
@@ -252,7 +290,9 @@ class DLPC485:
|
||||
port, baud, bytesize=bytesizes.get(dbsize, serial.EIGHTBITS),
|
||||
parity=parities.get(parity, serial.PARITY_NONE),
|
||||
stopbits=stopbits.get(sbits, serial.STOPBITS_ONE),
|
||||
timeout=0.15)
|
||||
timeout=0.15,
|
||||
write_timeout=1.0) # 写超时: 转换器停滞时 write 会
|
||||
# 永久阻塞并卡死线程, 必须设上限
|
||||
self.parser = FrameParser()
|
||||
self._open_key = target
|
||||
self.on_status(f'已连接 {port} @ {baud} '
|
||||
@@ -261,7 +301,11 @@ class DLPC485:
|
||||
self.ser = None
|
||||
with self._lock:
|
||||
self._target = None
|
||||
self.on_status(f'连接失败: {e}')
|
||||
et = str(e)
|
||||
if 'PermissionError' in repr(e) or '拒绝访问' in et \
|
||||
or 'Access is denied' in et:
|
||||
et += '(串口被占用: 检查串口助手/另一个本工具实例)'
|
||||
self.on_status(f'连接失败: {et}')
|
||||
if self.ser is not None:
|
||||
try:
|
||||
item = None
|
||||
@@ -276,10 +320,17 @@ class DLPC485:
|
||||
data = self.ser.read(256)
|
||||
if data:
|
||||
for fr in self.parser.feed(data):
|
||||
if self.on_frame is not None:
|
||||
self.on_frame(fr) # 结构化帧(二进制载荷场景用)
|
||||
self.on_event(f'← {describe(fr)} '
|
||||
f'[{fr.raw.hex(" ").upper()}]')
|
||||
except OSError:
|
||||
try:
|
||||
self.ser.close()
|
||||
except Exception:
|
||||
pass
|
||||
self.ser = None
|
||||
self._open_key = None # 必须同步清除, 否则同参数重连被当作已打开而跳过
|
||||
with self._lock:
|
||||
self._target = None
|
||||
self.on_status('串口丢失(设备拔出?), 请重新连接')
|
||||
@@ -5,7 +5,7 @@
|
||||
================================================================
|
||||
配合 demo 板当前 EDID 首选时序 800x600@60, 全屏纯色输出。
|
||||
用法: python gen_solid_colors.py → 输出到 ../assets/solid_*.png
|
||||
在 HDMI-Tool 中通过自定义栏位(⚙ 换图)加载, 发布包随附。
|
||||
在 DLPilot 中通过自定义栏位(⚙ 换图)加载, 发布包随附。
|
||||
"""
|
||||
|
||||
import os
|
||||
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 8.9 KiB After Width: | Height: | Size: 8.9 KiB |
@@ -23,6 +23,7 @@ import re
|
||||
import ssl
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
import uuid
|
||||
@@ -59,7 +60,10 @@ class Api:
|
||||
self.headers = {'Authorization': f'token {token}'}
|
||||
self.ctx = ssl._create_unverified_context() if insecure else None
|
||||
|
||||
def request(self, method, path, data=None, headers=None):
|
||||
def request(self, method, path, data=None, headers=None, retries=4):
|
||||
"""发请求; 实例 TLS 间歇性抽风(SSL UNEXPECTED_EOF), 连接级错误自动重试"""
|
||||
last = None
|
||||
for attempt in range(retries):
|
||||
req = urllib.request.Request(self.api + path, data=data, method=method,
|
||||
headers={**self.headers, **(headers or {})})
|
||||
try:
|
||||
@@ -68,6 +72,10 @@ class Api:
|
||||
return resp.status, json.loads(body) if body else None
|
||||
except urllib.error.HTTPError as e:
|
||||
return e.code, json.loads(e.read() or b'{}')
|
||||
except (urllib.error.URLError, ssl.SSLError, OSError) as e:
|
||||
last = e
|
||||
time.sleep(1.0 + attempt)
|
||||
raise RuntimeError(f'Gitea 请求连续 {retries} 次失败: {last}')
|
||||
|
||||
def get_json(self, path):
|
||||
status, data = self.request('GET', path)
|
||||
@@ -73,5 +73,19 @@ for _name, cmd, _note in d.PRESETS:
|
||||
d.build_frame(cmd)
|
||||
print(f'预设 {len(d.PRESETS)} 条全部可组帧: PASS')
|
||||
|
||||
# 8) M303/M304(SQ52205 电源实测)回归向量: 上游真机抓取帧
|
||||
check('build_frame(M303)', d.build_frame('M303').hex(' ').upper(),
|
||||
'D5 01 04 4D 33 30 33 E8')
|
||||
check('build_frame(M304)', d.build_frame('M304').hex(' ').upper(),
|
||||
'D5 01 04 4D 33 30 34 E9')
|
||||
frames = d.FrameParser().feed(bytes.fromhex('B5 F0 02 2E F4 14'))
|
||||
check('M303 应答帧', len(frames), 1)
|
||||
check('M303 电压(0x2EF4=12020mV)', d.format_power('M303', frames[0]),
|
||||
'12020 mV (12.02 V)')
|
||||
frames = d.FrameParser().feed(bytes.fromhex('B5 F0 02 00 34 26'))
|
||||
check('M304 应答帧', len(frames), 1)
|
||||
check('M304 电流(0x0034=+52mA)', d.format_power('M304', frames[0]), '+52 mA')
|
||||
check('M304 负值(FF06=-250mA 补码)', d.payload_s16_be(bytes.fromhex('FF 06')), -250)
|
||||
|
||||
print('RESULT:', 'FAIL:' + ','.join(fails) if fails else 'ALL PASS')
|
||||
sys.exit(1 if fails else 0)
|
||||
@@ -1,17 +0,0 @@
|
||||
{
|
||||
"area": "640x360",
|
||||
"slots": [
|
||||
{
|
||||
"name": "西门子星",
|
||||
"file": "bw_star.png"
|
||||
},
|
||||
{
|
||||
"name": "BW_ZONE",
|
||||
"file": "bw_zoneplate.png"
|
||||
},
|
||||
{
|
||||
"name": "BW_TEST",
|
||||
"file": "bw_testcard.png"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -12,7 +12,7 @@ PORT = sys.argv[1] if len(sys.argv) > 1 else 'COM9'
|
||||
FULL = len(sys.argv) > 2 and sys.argv[2].lower() == 'full'
|
||||
|
||||
BANK, REG = '60', '6E'
|
||||
STEPS = [ # (写值, 含义, 写入后 HDMI-Tool 应投的图, LA 预期变化 vs 基线)
|
||||
STEPS = [ # (写值, 含义, 写入后 DLPilot 应投的图, LA 预期变化 vs 基线)
|
||||
('00', '关闭交换(基准)', 'solid_0000AA / solid_000055',
|
||||
'D0:AA=13%/55=84%, D1:AA=72%/55=0.9% (基线)'),
|
||||
('80', 'B通道镜像', 'solid_0000AA',
|
||||
|
||||