From 9b29b6afd6794d48bbde3f79af4ff3bdd2cfc374 Mon Sep 17 00:00:00 2001 From: hulk Date: Fri, 18 Sep 2026 15:10:14 +0800 Subject: [PATCH] =?UTF-8?q?feat(DLPilot):=20M305=20=E5=AE=9E=E6=B5=8B?= =?UTF-8?q?=E8=B4=9F=E8=BD=BD=E5=8A=9F=E7=8E=87=E5=AF=B9=E6=8E=A5(?= =?UTF-8?q?=E5=8A=9F=E7=8E=87=E6=A3=80=E6=B5=8B=E4=B8=89=E6=AD=A5=E5=BA=8F?= =?UTF-8?q?=E5=88=97)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DLPilot/AGENTS.md | 12 +++++++----- DLPilot/README.md | 3 ++- DLPilot/src/app_gui.py | 25 ++++++++++++++++--------- DLPilot/src/dlpc_rs485.py | 19 +++++++++++++++---- DLPilot/src/hdmi_gui.py | 3 ++- DLPilot/src/selftest_rs485.py | 10 ++++++++++ 6 files changed, 52 insertions(+), 20 deletions(-) diff --git a/DLPilot/AGENTS.md b/DLPilot/AGENTS.md index 775eb04..1eea4da 100644 --- a/DLPilot/AGENTS.md +++ b/DLPilot/AGENTS.md @@ -106,18 +106,20 @@ DLPilot/ (DLPC 未就绪时 M730/M731/M732 被拒回 0xFF)以上游 `protocols.md` 为唯一权威, 本仓不维护协议文档副本;注意 **M7S0 是"开启"TEC 自动控温**(方向反直觉)、 M737 在定时投光中延迟应答(CLI 等待上限 35s)。 -- **M303/M304 载荷是二进制例外**(SQ52205 电源实测):成功载荷 2 字节大端原始值, +- **M303/M304/M305 载荷是二进制例外**(SQ52205 电源实测):成功载荷为大端原始值, **不是 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(真机抓取帧)。 + 上位机按有符号解析,FF 06=−250),**M305=u32 mW(LEN=4,勿复用双字节解析; + 恒非负,量程 0~295000,能量方向看 M304 符号;显示 mW→W 两位小数)**。 + 封装在 `payload_u16_be/payload_s16_be/payload_u32_be/format_power`, + 回归向量在 selftest_rs485.py(M303/M304 真机帧,M305 算法推演帧)。 - **电源自检序列**(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;**功率值暂不计算** - (用户要求:后续固件会提供功率命令直接返回,届时填充),显示占位"功率: —"。 +- **功率检测** = M303+M304+M305 序列 → 读数框 电压V / 电流mA / 功率W(M305 u32 mW + 换算两位小数);各步载荷长度按 kind 校验(2/2/4 字节),坏帧中止显示 ERR。 ## 5. 构建 / 发布流水线(全部在 src/ 下执行) diff --git a/DLPilot/README.md b/DLPilot/README.md index aae53c5..905395f 100644 --- a/DLPilot/README.md +++ b/DLPilot/README.md @@ -104,7 +104,7 @@ GUI"光机控制"区:选串口 → 连接 → 点快捷按钮或命令下拉/ 按实测电压就近判定 PD 固定挡(5/9/12/15/20/28V,容差 ±5%):**12V 挡且 ≥3A 绿底 "FPDO: 12V@3A"**;挡位不符(如 9V 挡)**红底**显示实际挡位;不落在任何标准挡显示 **ERR**(CH224Q 电压配置寄存器只写不可读,挡位只能靠实测判定)。**功率检测**依序发 -M303+M304,读数框显示 电压(V) / 电流(mA);功率暂留空(后续固件命令直接返回)。 +M303+M304+M305,读数框显示 电压(V) / 电流(mA) / 功率(W,mW 换算两位小数)。 | 命令 | 功能 | 响应示例 | |---|---|---| @@ -117,6 +117,7 @@ M303+M304,读数框显示 电压(V) / 电流(mA);功率暂留空(后续固 | `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(正=正向) | +| `M305` | 查负载功率(SQ52205 实测,**4 字节大端二进制载荷** u32) | mW→显示 W 两位小数:0x00008CA0=36000 mW=36.00 W(量程 0~295000 mW) | | `M999` | 查固件版本 | `okv20.0.0.1` | | `M737` | 查投光是否结束(投光中延迟应答) | `ok` | | `M7S0` / `M7S1` | TEC 自动控温开 / 关(注意 S0=开) | `ok` | diff --git a/DLPilot/src/app_gui.py b/DLPilot/src/app_gui.py index 9bf4d90..2d2d4a6 100644 --- a/DLPilot/src/app_gui.py +++ b/DLPilot/src/app_gui.py @@ -997,22 +997,22 @@ class App(ctk.CTk): self.var_fpdo.set(f'FPDO: {gear}V@{a_txt}A') def _pwr_query(self): - """功率检测: M303 实测电压 + M304 实测电流(按有符号); 功率值待固件功率命令""" + """功率检测: M303 电压 + M304 电流(按有符号) + M305 功率(u32 mW)""" 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_seq = [('M303', 'mv'), ('M304', 'ma'), ('M305', 'mw')] 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._pwr_update(self._pwr_values.get('mv'), + self._pwr_values.get('ma'), + self._pwr_values.get('mw')) self._dlpc_log('功率检测: 完成') return self.dlpc.send(self._pwr_seq[0][0]) @@ -1025,8 +1025,10 @@ class App(ctk.CTk): self.after_cancel(self._pwr_timer) self._pwr_timer = None cmd, kind = self._pwr_seq.pop(0) + # 各步载荷长度: 电压/电流 2 字节, 功率 4 字节(u32) + want_len = {'mv': 2, 'ma': 2, 'mw': 4}[kind] 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: + if fr is None or fr.type_code != 0xF0 or len(raw) != want_len: self._pwr_seq.clear() self.var_pwr.set('电压: ERR 电流: — 功率: —') self._dlpc_log('功率检测: 应答错误/超时, 中止') @@ -1035,11 +1037,16 @@ class App(ctk.CTk): self._pwr_values['mv'] = dlpc_rs485.payload_u16_be(raw) elif kind == 'ma': self._pwr_values['ma'] = dlpc_rs485.payload_s16_be(raw) + elif kind == 'mw': + self._pwr_values['mw'] = dlpc_rs485.payload_u32_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 _pwr_update(self, v_mv, ma, mw): + """功率读数框更新: 电压 V / 电流 mA / 功率 W(mW 换算, 两位小数; 缺项显示 —)""" + v = f'{v_mv / 1000:.2f} V' if v_mv is not None else '—' + a = f'{ma} mA' if ma is not None else '—' + w = f'{mw / 1000:.2f} W' if mw is not None else '—' + self.var_pwr.set(f'电压: {v} 电流: {a} 功率: {w}') def _light_entry_apply(self, _evt=None): """滑条数值输入框回车: 校验并限幅 54-800, 同步回滑条""" diff --git a/DLPilot/src/dlpc_rs485.py b/DLPilot/src/dlpc_rs485.py index 437e38f..c22c087 100644 --- a/DLPilot/src/dlpc_rs485.py +++ b/DLPilot/src/dlpc_rs485.py @@ -8,7 +8,7 @@ DLPC3421 光机 RS485 控制链路(板上 GD32E230 作 RS485→I2C 网关) 物理层 RS485 半双工 115200 8N1(PC 经 USB-RS485 转换器接板, DE 由 MCU 硬件换向) 命令帧 D5 01 LEN CRC 01=板类型, LEN=命令字节数 响应帧 B5 TYPE LEN CRC payload 为 ASCII(ok / err:OL / okTL:246 ...), - 例外: M303/M304 载荷为 2 字节大端二进制 + 例外: M303/M304/M305 载荷为大端二进制 (SQ52205 电源实测, 见 POWER_COMMANDS) CRC 帧内第 2 字节(板类型)起累加到最后一个 payload 字节, 取低 8 位 帧长 最小 6 字节, 最大 32 字节 @@ -83,12 +83,14 @@ PRESETS = ( Frame = collections.namedtuple('Frame', 'type_code payload raw') -# --- M303/M304(SQ52205 电源实测值查询): 成功载荷为 2 字节原始二进制(非 ASCII) --- +# --- M303/M304/M305(SQ52205 电源实测值查询): 成功载荷为原始二进制(非 ASCII) --- # M303 母线电压: u16 大端, 单位 mV; 12V 轨正常窗口 11400~12600(标称 ±5%) # M304 负载电流: s16 大端补码, 单位 mA; 正=正向, 负=反向(固件原样上总线, # 上位机必须按有符号解析)。例: 载荷 FF 06 = -250 mA +# M305 负载功率: u32 大端(LEN=4, 勿复用双字节解析), 单位 mW, 恒非负; +# 量程约 0~295000 mW; 能量方向看 M304 符号 # 错误时 TYPE=0xFF, 载荷为错误信息字节串(走通用译码路径) -POWER_COMMANDS = ('M303', 'M304') +POWER_COMMANDS = ('M303', 'M304', 'M305') def payload_bytes(fr): @@ -105,11 +107,20 @@ def payload_s16_be(raw): return v - 0x10000 if v & 0x8000 else v +def payload_u32_be(raw): + return (raw[0] << 24) | (raw[1] << 16) | (raw[2] << 8) | raw[3] + + def format_power(cmd, fr): - """M303/M304 成功应答 -> 人读文本; 非 POWER 命令/失败/载荷异常返回 None""" + """电源实测命令成功应答 -> 人读文本; 非电源命令/失败/载荷异常返回 None""" if cmd not in POWER_COMMANDS: return None raw = payload_bytes(fr) + if cmd == 'M305': + if fr.type_code != 0xF0 or len(raw) != 4: + return None + mw = payload_u32_be(raw) + return f'{mw} mW ({mw / 1000:.2f} W)' if fr.type_code != 0xF0 or len(raw) != 2: return None if cmd == 'M303': diff --git a/DLPilot/src/hdmi_gui.py b/DLPilot/src/hdmi_gui.py index 8da36a3..1fd0d85 100644 --- a/DLPilot/src/hdmi_gui.py +++ b/DLPilot/src/hdmi_gui.py @@ -121,7 +121,8 @@ def run_dlpc(port, baud, cmds): print('← (35s 超时无响应)') rc = 1 else: - print(f'← {dlpc_rs485.describe(got)} [{got.raw.hex(" ").upper()}]') + nice = dlpc_rs485.format_power(cmd, got) # 电源命令二进制载荷译码 + print(f'← {nice or dlpc_rs485.describe(got)} [{got.raw.hex(" ").upper()}]') if got.type_code != 0xF0: rc = 1 ser.close() diff --git a/DLPilot/src/selftest_rs485.py b/DLPilot/src/selftest_rs485.py index 4f52e82..13ea406 100644 --- a/DLPilot/src/selftest_rs485.py +++ b/DLPilot/src/selftest_rs485.py @@ -87,5 +87,15 @@ 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) +# 9) M305(SQ52205 实测功率)回归向量: u32 大端 mW(LEN=4, 算法推演帧) +check('build_frame(M305)', d.build_frame('M305').hex(' ').upper(), + 'D5 01 04 4D 33 30 35 EA') +frames = d.FrameParser().feed(bytes.fromhex('B5 F0 04 00 00 8C A0 20')) +check('M305 应答帧', len(frames), 1) +check('M305 功率(0x8CA0=36000mW)', d.format_power('M305', frames[0]), + '36000 mW (36.00 W)') +check('M305 拒绝双字节载荷', d.format_power( + 'M305', d.FrameParser().feed(bytes.fromhex('B5 F0 02 8C A0 1E'))[0]), None) + print('RESULT:', 'FAIL:' + ','.join(fails) if fails else 'ALL PASS') sys.exit(1 if fails else 0)