Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
296e220f20 | ||
|
|
e3ef359df4 | ||
|
|
be32efa806 |
@@ -0,0 +1,3 @@
|
||||
[submodule "gd32e230f8_firmware_merge_tool"]
|
||||
path = gd32e230f8_firmware_merge_tool
|
||||
url = https://gitea.hulk.wang/hulk/gd32e230f8_firmware_merge_tool.git
|
||||
+26
-1
@@ -42,6 +42,8 @@ DLPilot/
|
||||
| `src/config.py` | 运行目录定位(BASE_DIR)与 pattern_slots.json 读写(slots/area/serial 三键) |
|
||||
| `src/dlpc_rs485.py` | 光机 RS485 协议模块: 帧构造/增量解析/译码表/预设命令/串口线程(DLPC485) |
|
||||
| `src/selftest_rs485.py` | 光机协议自检(对照上游实测帧向量, 无硬件回归, 改协议代码后必跑) |
|
||||
| `src/selftest_theme.py` | 主题切换回归(离屏, swatch 底色/渲染像素/标题字号, 改样式后必跑) |
|
||||
| `src/selftest_log.py` | 收发日志上限与队列消费回归(离屏, 改 _dlpc_log/_poll_dlpc 后必跑) |
|
||||
| `src/build_exe.py` | PyInstaller 打包 → `release/DLPilot.exe`(onefile/noconsole, 内嵌图标+pyserial+PySide6-Fluent-Widgets) |
|
||||
| `src/package_release.py` | 复制图案到 release/(平铺) → `release/DLPilot-v<ver>-win64.zip`(校验栏位引用完整性; release/ 与 zip 内容一致) |
|
||||
| `src/publish_gitea.py` | 建/复用 Gitea Release 并上传 zip(tag 默认取 zip 文件名) |
|
||||
@@ -174,6 +176,8 @@ python publish_gitea.py ../release/DLPilot-v<版本>-win64.zip --replace --insec
|
||||
2. `python selftest_rs485.py` —— 光机协议回归(改 dlpc_rs485.py 后必跑,向量来自上游 protocols.md);
|
||||
3. `python selftest_theme.py` —— 主题切换回归(改 app_fluent.py 涉及样式/主题后必跑;
|
||||
离屏渲染,Dark/Light 往返后校验 swatch 底色样式与渲染像素、标题字号);
|
||||
3b. `python selftest_log.py` —— 收发日志上限/队列消费回归(改 `_dlpc_log`/`_poll_dlpc`
|
||||
后必跑;600 行封顶 500、坏条目不中断 drain);
|
||||
4. `python hdmi_gui.py --seconds 5` —— GUI 冒烟(自动开关);
|
||||
5. `python hdmi_gui.py --test-pattern Checker --area 800x600 --seconds 3` —— 投图链路;
|
||||
6. `python hdmi_gui.py --dump` —— EDID 解算不抛异常;
|
||||
@@ -229,4 +233,25 @@ python publish_gitea.py ../release/DLPilot-v<版本>-win64.zip --replace --insec
|
||||
位于电流行与预设行之间(命令直发 `_dlpc_send`,无新增协议逻辑)。
|
||||
验证:compileall / selftest_rs485 / selftest_theme ALL PASS;离屏渲染断言"清空"
|
||||
与标题垂直中心差 0px、7 个快捷按钮齐全;真实桌面冒烟 exit 0。APP_VERSION → 0.4.2
|
||||
并重打包。
|
||||
并重打包。v0.4.2 已发布:tag `DLPilot-v0.4.2` 指向提交 `7bd9943`(源码+exe+文档
|
||||
同一提交),资产 `DLPilot-v0.4.2-win64.zip`(66.8 MB,zip 内为预置 COM15 配置,
|
||||
本机 COM5 运行态已还原未入库),
|
||||
https://gitea.hulk.wang/hulk/HW-Debug-Tools/releases/tag/DLPilot-v0.4.2 。
|
||||
本次发布说明已用 --notes-file 传入(临时文件,未入库)。
|
||||
- 2026-10-13 v0.4.3 修复"长会话越跑越卡、命令点了没反应"(FilmForge 同案):
|
||||
根因是 `_dlpc_log` 的手工 textCursor 裁剪用了**实例级枚举访问**
|
||||
(`cursor.Start`/`cursor.BlockUnderCursor`),PySide6 6.11 移除该形式,收发日志
|
||||
超 500 行后每次追加必抛 AttributeError;异常又逃出 `_poll_dlpc` 的 drain 循环
|
||||
(旧 try 只捕获 queue.Empty),每 tick 最多消费到第一条 event,`_dlpc_log_q`
|
||||
棘轮积压、日志裁剪/自动滚动全失效;电源自检/功率检测序列在 `_pd_step_next()`
|
||||
之前的 `_dlpc_log` 调用处被打断,`_pd_seq` 永不清空,后续所有手动命令被
|
||||
"序列进行中"拦截直到断开重连。整改:①改用 `setMaximumBlockCount(500)` 环形
|
||||
裁剪(O(1),不依赖枚举写法);②`_poll_dlpc` 改逐事件兜底 try/except,单条异常
|
||||
只打栈放弃该条、不再中断本轮消费;③新增 `selftest_log.py` 回归(600 行封顶 +
|
||||
坏条目 drain 不中断)。验证:compileall / selftest_rs485 / selftest_theme /
|
||||
selftest_log ALL PASS;GUI 冒烟 exit 0;`--dump` / `--dlpc-cmd`(无串口)正常。
|
||||
APP_VERSION → 0.4.3 重打包(67.1 MB),按 §8 约定临时还原预置 pattern_slots.json
|
||||
后 `package_release.py 0.4.3` 生成 `DLPilot-v0.4.3-win64.zip`(66.8 MB,zip 内
|
||||
为预置 COM15 配置,本机 COM5 运行态已还原未入库)。遗留(未随本版处理):
|
||||
`dlpc_rs485.py` 的 `except OSError` 把写超时(SerialTimeoutException)与设备
|
||||
拔出混判,一次转换器停滞即断链误报"设备拔出"。
|
||||
|
||||
@@ -13,6 +13,7 @@ import queue
|
||||
import re
|
||||
import sys
|
||||
import time
|
||||
import traceback
|
||||
|
||||
# PyInstaller keeps Shiboken's ABI DLLs in a sibling directory. Register both
|
||||
# Qt and Shiboken before importing QtCore; this is required by one-file builds
|
||||
@@ -63,7 +64,7 @@ import edid_parse
|
||||
import patterns
|
||||
|
||||
APP_NAME = "DLPilot"
|
||||
APP_VERSION = "0.4.2"
|
||||
APP_VERSION = "0.4.3"
|
||||
REPO_URL = "https://gitea.hulk.wang/hulk/HW-Debug-Tools"
|
||||
MONO = QFont("Consolas", 10)
|
||||
CHIP_IDLE = "#666666"
|
||||
@@ -440,6 +441,10 @@ class App(QMainWindow):
|
||||
trailing=self._button("清空", self._dlpc_clear))
|
||||
self.txt_dlpc = self._text()
|
||||
self.txt_dlpc.setMinimumHeight(230)
|
||||
# 收发日志环形裁剪: 超 500 行自动丢最旧(O(1))。勿改回 textCursor 手工裁剪——
|
||||
# PySide6 6.11 移除实例级枚举访问, cursor.Start 必抛 AttributeError 且异常会
|
||||
# 打断 _poll_dlpc 的队列消费(事件棘轮积压), 回归见 selftest_log.py
|
||||
self.txt_dlpc.document().setMaximumBlockCount(500)
|
||||
body.addWidget(self.txt_dlpc)
|
||||
self._dlpc_log("帧格式: D5 01 LEN <命令> CRC / B5 TYPE LEN <payload> CRC")
|
||||
layout.addWidget(card)
|
||||
@@ -755,9 +760,12 @@ class App(QMainWindow):
|
||||
def _query_lamp(self): self._tl_order.append('LAMP'); self._dlpc_send('M202')
|
||||
|
||||
def _poll_dlpc(self):
|
||||
try:
|
||||
while True:
|
||||
while True:
|
||||
try:
|
||||
kind, value = self._dlpc_log_q.get_nowait()
|
||||
except queue.Empty:
|
||||
break
|
||||
try:
|
||||
if kind == 'status':
|
||||
self.lbl_dlpc_st.setText(value)
|
||||
if value.startswith(('已断开', '串口丢失')): self._serial_reset_state()
|
||||
@@ -766,15 +774,16 @@ class App(QMainWindow):
|
||||
elif kind == 'pdframe': self._pd_step_done(value.type_code == 0xF0, value)
|
||||
elif kind == 'pwrframe': self._pwr_step_done(value)
|
||||
else: self._dlpc_log(value)
|
||||
except queue.Empty: pass
|
||||
except Exception:
|
||||
# 单条事件处理异常只打栈放弃该条, 不得中断本轮消费——否则积压随事件流
|
||||
# 单调增长(v0.4.3 前日志裁剪异常即此路径, 显示延迟越跑越大)
|
||||
traceback.print_exc()
|
||||
connected = self.dlpc.is_connected()
|
||||
if connected != self._dlpc_connected:
|
||||
self._dlpc_connected = connected; self.btn_dlpc.setText("断开" if connected else "连接")
|
||||
|
||||
def _dlpc_log(self, line):
|
||||
self.txt_dlpc.appendPlainText(time.strftime('%H:%M:%S ') + line)
|
||||
if self.txt_dlpc.blockCount() > 500:
|
||||
cursor = self.txt_dlpc.textCursor(); cursor.movePosition(cursor.Start); cursor.select(cursor.BlockUnderCursor); cursor.removeSelectedText(); cursor.deleteChar()
|
||||
self.txt_dlpc.verticalScrollBar().setValue(self.txt_dlpc.verticalScrollBar().maximum())
|
||||
|
||||
def _dlpc_clear(self): self.txt_dlpc.clear()
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
# coding: utf-8
|
||||
"""收发日志上限与队列消费回归自检(无窗口, 离屏渲染)。
|
||||
|
||||
背景: v0.4.3 之前收发日志用 textCursor 手工裁剪, 其中 `cursor.Start` /
|
||||
`cursor.BlockUnderCursor` 是实例级枚举访问 —— PySide6 6.11 移除了该形式,
|
||||
日志一旦超过 500 行, 每次 _dlpc_log 追加后必抛 AttributeError; 且异常会
|
||||
逃出 _poll_dlpc 的 drain 循环(旧 try 只捕获 queue.Empty), 每 tick 最多
|
||||
消费到第一条 event, `_dlpc_log_q` 棘轮积压、日志裁剪与自动滚动全部失效,
|
||||
电源自检/功率检测序列还会被异常打断在启动处(之后所有命令被"序列进行中"
|
||||
拦截)。v0.4.3 改为 setMaximumBlockCount 环形裁剪 + 逐事件异常兜底。
|
||||
|
||||
本测试保证:
|
||||
1. 连续追加 600 行日志不抛异常, document().blockCount() 环形封顶 500, 末行在;
|
||||
2. 单条异常事件(模拟未来引入的坏数据)不中断 _poll_dlpc 本轮队列消费
|
||||
—— 其后的事件仍全部入日志、队列清空。
|
||||
|
||||
运行: python selftest_log.py (依赖 PySide6-Fluent-Widgets 与 pyserial ——
|
||||
串口卡构建后 txt_dlpc 才存在)
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
|
||||
os.environ.setdefault("QT_QPA_PLATFORM", "offscreen")
|
||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||
|
||||
from PySide6.QtWidgets import QApplication
|
||||
from qfluentwidgets import Theme, setTheme
|
||||
|
||||
import app_fluent
|
||||
import dlpc_rs485
|
||||
|
||||
|
||||
def check_cap(win):
|
||||
for i in range(600):
|
||||
win._dlpc_log(f"cap-line-{i}")
|
||||
blocks = win.txt_dlpc.document().blockCount()
|
||||
has_last = "cap-line-599" in win.txt_dlpc.toPlainText()
|
||||
print(f" 追加 600 行: blockCount={blocks}(封顶 500), 末行在: {has_last}")
|
||||
return blocks <= 500 and has_last
|
||||
|
||||
|
||||
def check_drain_resilience(win):
|
||||
win._timer.stop() # 排除 80ms 轮询定时器干扰
|
||||
win._dlpc_log_q.put(("pdframe", "poison-not-a-frame")) # 该条分发必抛异常
|
||||
win._dlpc_log_q.put(("event", "after-poison-1"))
|
||||
win._dlpc_log_q.put(("event", "after-poison-2"))
|
||||
win._poll_dlpc() # 单次手动驱动(不进事件循环)
|
||||
left = win._dlpc_log_q.qsize()
|
||||
text = win.txt_dlpc.toPlainText()
|
||||
drained = left == 0 and "after-poison-1" in text and "after-poison-2" in text
|
||||
print(f" 坏条目后消费不中断: 残留队列={left}, 后续事件全部写入: {drained}")
|
||||
return drained
|
||||
|
||||
|
||||
def main():
|
||||
if not dlpc_rs485.HAS_SERIAL:
|
||||
print("FAILED 缺少 pyserial: 串口卡未构建, txt_dlpc 不存在")
|
||||
return 1
|
||||
app = QApplication.instance() or QApplication(sys.argv)
|
||||
setTheme(Theme.AUTO)
|
||||
win = app_fluent.App(0)
|
||||
passed = check_cap(win)
|
||||
passed &= check_drain_resilience(win)
|
||||
win.close()
|
||||
print("ALL PASS" if passed else "FAILED")
|
||||
return 0 if passed else 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -12,6 +12,7 @@
|
||||
| <img src="EDID-viewer/assets/edid-viewer.png" width="64" alt="EDID-Viewer 图标"/> | [EDID-viewer](EDID-viewer/) | Windows 显示器 EDID 只读查看器:活动设备或离线文件的厂家/序列号/分辨率时序、CTA/HDMI/音频/HDR 能力、校验诊断;BIN/HEX 导入,BIN/JSON 报告导出 | 到 [Releases](https://gitea.hulk.wang/hulk/HW-Debug-Tools/releases) 下 `EDID-Viewer-vX.Y.Z-win64.zip`,解压即用;详见 [EDID-viewer/README.md](EDID-viewer/README.md) |
|
||||
| <img src="SerialMate/assets/serial-mate.png" width="64" alt="SerialMate 图标"/> | [SerialMate](SerialMate/) | RS485/串口调试助手:收发 + 协议包解析(帧构成令牌序列/包头/板ID/LEN口径/CRC 全可定制,默认兼容 DLPilot 光机协议)、板型档案一键切换协议参数与命令预设、payload 字符串/HEX 显示、4 通道曲线(弹窗独立、时间窗滑笔回看、悬停读值、暂停渲染)、发送框输入 payload 自动包装组帧(`/` 分隔多条)、HEX 直发与周期发送 | 到 [Releases](https://gitea.hulk.wang/hulk/HW-Debug-Tools/releases) 下 `SerialMate-vX.Y.Z-win64.zip`,解压即用;详见 [SerialMate/README.md](SerialMate/README.md) |
|
||||
| <img src="I2C-Inject/logos/logo_i2c_orange.png" width="64" alt="I2C-Inject 图标"/> | [I2C-Inject](I2C-Inject/) | ⏸ **暂停开发**:LT8619C 寄存器在线读写(RP2040 注入器在 demo 板 I2C 总线上择机注入,避让主控轮询,写后自动读回校验)。代码与固件保留备查 | 暂不提供发布;状态与原理详见 [I2C-Inject/README.md](I2C-Inject/README.md) |
|
||||
| <img src="gd32e230f8_firmware_merge_tool/FlashTool/src/logos/logo_flash.png" width="64" alt="GD32 固件工具图标"/> | [gd32e230f8_firmware_merge_tool](gd32e230f8_firmware_merge_tool/) | 🔗 **子模块**(独立仓库独立管理):GD32E230 固件工具——APP/BL 合成补齐(`_UPDATE.bin`/`_BL.bin`)+ DAPLink Flash GUI 烧录(自带 OpenOCD 工具链) | 本仓目录即快捷入口;版本更新与发布在其[原仓库](https://gitea.hulk.wang/hulk/gd32e230f8_firmware_merge_tool)(Releases 下 zip 解压即用);详见 [README](gd32e230f8_firmware_merge_tool/README.md) |
|
||||
|
||||
## 环境要求
|
||||
|
||||
@@ -50,6 +51,7 @@ HW-Debug-Tools/
|
||||
│ ├── README.md # 使用说明
|
||||
│ └── AGENTS.md # 工程档案
|
||||
├── I2C-Inject/ # PC 端 GUI + RP2040 固件 + 打包 exe + 图标 + README
|
||||
├── gd32e230f8_firmware_merge_tool/ # 🔗 git 子模块 → GD32E230 固件工具(独立仓库,见下方备注)
|
||||
├── LICENSE
|
||||
└── README.md
|
||||
```
|
||||
@@ -68,6 +70,10 @@ python publish_gitea.py <工具>-v<版本>-win64.zip --insecure # 上传 Gite
|
||||
## 备注
|
||||
|
||||
- 源码与打包产物(exe、pattern_slots.json)均入库;发布 zip 不入库,只上传 Gitea Release
|
||||
- `gd32e230f8_firmware_merge_tool/` 是 git 子模块([独立仓库](https://gitea.hulk.wang/hulk/gd32e230f8_firmware_merge_tool),
|
||||
代码与发布均在其仓内管理,本仓只锁定指针):
|
||||
新克隆后需 `git submodule update --init` 拉取内容;
|
||||
跟进其最新版用 `git submodule update --remote gd32e230f8_firmware_merge_tool`,随本仓提交新指针
|
||||
- 两件工具的寄存器语义(0x606E 位交换 / 0x606D 整组顺序)均经 demo 板逻辑分析仪
|
||||
双色交叉实测,过程数据与规程存于上游主仓库 `doc/architecture/`
|
||||
- 更新:2026-09-28
|
||||
- 更新:2026-09-30
|
||||
|
||||
@@ -29,6 +29,11 @@
|
||||
自动包装发送。
|
||||
- **I2C-Inject**:RP2040 固件 + PC GUI,demo 板 I2C 总线择机注入读写寄存器。
|
||||
**2026-09-25 起暂停开发**,代码与固件保留备查。
|
||||
- 子模块引用:`gd32e230f8_firmware_merge_tool/`(2026-09-30 接入,git submodule 指向
|
||||
`hulk/gd32e230f8_firmware_merge_tool`)——GD32E230 APP/BL 合成补齐
|
||||
(`_UPDATE.bin`/`_BL.bin`)+ DAPLink Flash GUI 烧录。代码、版本与发布均在原仓库
|
||||
独立管理,本仓只锁指针;新克隆 `git submodule update --init` 拉取,跟进上游
|
||||
`git submodule update --remote gd32e230f8_firmware_merge_tool` 后随本仓提交新指针。
|
||||
- 镜像约定:`EDID-viewer` 主目录(`E:\Hulk-Coding\HW-Debug-Tools\EDID-viewer`)
|
||||
改动后需 robocopy /MIR 同步到 `E:\Hulk-Coding\LT8619C\_Debug\_Tools\EDID-viewer`
|
||||
(排除 .venv/build/__pycache__/.git,zip 不同步)。
|
||||
|
||||
Submodule
+1
Submodule gd32e230f8_firmware_merge_tool added at d776979a5a
Reference in New Issue
Block a user