diff --git a/app/services/pattern_service.py b/app/services/pattern_service.py index d8a3e6b..bd3d728 100644 --- a/app/services/pattern_service.py +++ b/app/services/pattern_service.py @@ -5,7 +5,6 @@ from dataclasses import dataclass from app.data_range_converter import convert_pattern_params from app.pq.pq_config import get_pattern -from drivers.ucd_helpers import send_solid_rgb_pattern @dataclass @@ -62,11 +61,11 @@ class PatternService: mode=mode, converted_params=converted_params ) self.app.ucd.set_ucd_params(active_config) - success = self.app.ucd.apply_signal_format( + success = self.app.signal_service.update_signal_format( color_space=self.app.sdr_color_space_var.get(), data_range=data_range, bit_depth=self.app.sdr_bit_depth_var.get(), - color_format=self.app.sdr_output_format_var.get(), + output_format=self.app.sdr_output_format_var.get(), ) if log_details: self._log(f"SDR 信号格式设置{'成功' if success else '失败'}", "success" if success else "error") @@ -94,11 +93,11 @@ class PatternService: mode=mode, converted_params=converted_params ) self.app.ucd.set_ucd_params(active_config) - success = self.app.ucd.apply_signal_format( + success = self.app.signal_service.update_signal_format( color_space=self.app.hdr_color_space_var.get(), data_range=data_range, bit_depth=self.app.hdr_bit_depth_var.get(), - color_format=self.app.hdr_output_format_var.get(), + output_format=self.app.hdr_output_format_var.get(), max_cll=self.app.hdr_maxcll_var.get(), max_fall=self.app.hdr_maxfall_var.get(), ) @@ -135,7 +134,7 @@ class PatternService: log_details=False, ) converted_rgb = self._convert_rgb_for_test_type(rgb, active_session.test_type) - send_solid_rgb_pattern(self.app.ucd, converted_rgb, raise_on_error=True) + self.app.signal_service.send_solid_rgb(converted_rgb) return True def _get_source_pattern_params(self, mode): diff --git a/app/services/ucd_service.py b/app/services/ucd_service.py index 925c0a6..4625f67 100644 --- a/app/services/ucd_service.py +++ b/app/services/ucd_service.py @@ -125,6 +125,48 @@ class SignalService: def send_image(self, path: str) -> None: self.send_pattern(image_pattern(path)) + # -- 过渡期 API(Phase 2)----------------------------------- + # 现有 GUI 回调以"仅更新信号格式、不切换图案"的方式调用 + # ``ucd.apply_signal_format(color_space=..., color_format=..., bit_depth=...)``。 + # 新代码统一通过本方法走 SignalService;内部仍委托给底层 + # controller 的同名旧接口,迁移完成后将替换为纯净实现。 + + def update_signal_format( + self, + *, + color_space: str, + output_format: str, + bit_depth: str, + data_range: str = "Full", + max_cll: int | None = None, + max_fall: int | None = None, + ) -> bool: + """仅将信号格式 stage 到 SDK(沿用上一次的 timing),不切换图案。 + + UI 字符串先经域层解析做参数校验;解析失败抛 :class:`UcdConfigError`。 + """ + # 解析仅做校验;当前实现走 raw controller 的旧 API + _ = build_signal_format( + color_space=color_space, + output_format=output_format, + bit_depth=bit_depth, + data_range=data_range, + ) + ctrl = getattr(self._dev, "raw_controller", None) + if ctrl is None: + raise UcdError("update_signal_format 暂仅支持 UCD323Device") + with self._lock: + return bool( + ctrl.apply_signal_format( + color_space=color_space, + color_format=output_format, + bit_depth=bit_depth, + data_range=data_range, + max_cll=max_cll, + max_fall=max_fall, + ) + ) + # -- 透传给上层的查询 --------------------------------------- @property diff --git a/app/tests/local_dimming.py b/app/tests/local_dimming.py index f0c17a3..83d1864 100644 --- a/app/tests/local_dimming.py +++ b/app/tests/local_dimming.py @@ -18,7 +18,7 @@ from tkinter import filedialog, messagebox import numpy as np from PIL import Image -from drivers.ucd_helpers import get_current_resolution, send_image_pattern +from drivers.ucd_helpers import get_current_resolution # -------------------------------------------------------------------------- @@ -130,8 +130,10 @@ def start_local_dimming_test(self): log(f" 图像生成失败: {e}", level="error") continue - if not send_image_pattern(self.ucd, image_path): - log(f" {percentage}% 窗口发送失败,跳过", level="error") + try: + self.signal_service.send_image(image_path) + except Exception as exc: + log(f" {percentage}% 窗口发送失败: {exc},跳过", level="error") continue log(f"等待 {wait_time} 秒...", level="info") @@ -194,7 +196,11 @@ def send_ld_window(self, percentage): except Exception as e: self._dispatch_ui(self.log_gui.log, f"图像生成失败: {e}") return - ok = send_image_pattern(self.ucd, image_path) + try: + self.signal_service.send_image(image_path) + ok = True + except Exception: + ok = False msg = ( f"{percentage}% 窗口已发送" if ok else f"{percentage}% 窗口发送失败" diff --git a/app/views/panels/ai_image_panel.py b/app/views/panels/ai_image_panel.py index 2bc2545..be6f9e6 100644 --- a/app/views/panels/ai_image_panel.py +++ b/app/views/panels/ai_image_panel.py @@ -13,7 +13,7 @@ import ttkbootstrap as ttk from PIL import Image, ImageTk from app.services import ai_image as _svc -from drivers.ucd_helpers import get_current_resolution, send_image_pattern +from drivers.ucd_helpers import get_current_resolution # ---------------- 面板创建 ---------------- @@ -689,7 +689,8 @@ def _send_to_ucd(self): def _worker(): err = None try: - ok = send_image_pattern(ucd, send_path) + self.signal_service.send_image(send_path) + ok = True except Exception as exc: ok = False err = str(exc) diff --git a/app/views/panels/main_layout.py b/app/views/panels/main_layout.py index 45214a5..c7a1355 100644 --- a/app/views/panels/main_layout.py +++ b/app/views/panels/main_layout.py @@ -661,11 +661,11 @@ def on_sdr_output_format_changed(self, event=None): return if getattr(self.ucd, "status", False): - ok = self.ucd.apply_signal_format( + ok = self.signal_service.update_signal_format( color_space=self.sdr_color_space_var.get(), data_range=self.sdr_data_range_var.get(), bit_depth=self.sdr_bit_depth_var.get(), - color_format=fmt, + output_format=fmt, ) if not ok: self.log_gui.log("SDR色彩格式应用到UCD失败", level="error") @@ -685,13 +685,13 @@ def on_hdr_output_format_changed(self, event=None): return if getattr(self.ucd, "status", False): - ok = self.ucd.apply_signal_format( + ok = self.signal_service.update_signal_format( color_space=self.hdr_color_space_var.get(), data_range=self.hdr_data_range_var.get(), bit_depth=self.hdr_bit_depth_var.get(), max_cll=self.hdr_maxcll_var.get(), max_fall=self.hdr_maxfall_var.get(), - color_format=fmt, + output_format=fmt, ) if not ok: self.log_gui.log("HDR色彩格式应用到UCD失败", level="error") diff --git a/app/views/panels/single_step_panel.py b/app/views/panels/single_step_panel.py index 848bcf8..c23c33b 100644 --- a/app/views/panels/single_step_panel.py +++ b/app/views/panels/single_step_panel.py @@ -13,7 +13,7 @@ from tkinter import filedialog, messagebox import ttkbootstrap as ttk from PIL import Image -from drivers.ucd_helpers import get_current_resolution, send_image_pattern +from drivers.ucd_helpers import get_current_resolution _DEFAULT_SAMPLES = [ @@ -409,9 +409,7 @@ def _send_current_patch(self): def worker(): try: image_path = _build_color_patch(self, sample["hex"]) - ok = send_image_pattern(self.ucd, image_path) - if not ok: - raise RuntimeError("UCD323 发送失败") + self.signal_service.send_image(image_path) self.single_step_current_image_path = image_path self._dispatch_ui( self.single_step_status_var.set, diff --git a/drivers/ucd_driver.py b/drivers/ucd_driver.py index 5f68b17..7f09bb4 100644 --- a/drivers/ucd_driver.py +++ b/drivers/ucd_driver.py @@ -250,17 +250,19 @@ class UCD323Device(IUcdDevice): def set_pattern(self, pattern: PatternSpec) -> None: with self._lock: - if self._state not in (UcdState.CONFIGURED, UcdState.APPLIED): - raise UcdStateError( - f"set_pattern 需要 CONFIGURED/APPLIED 状态,当前 {self._state.name}" - ) + # Phase 2 过渡:允许从 OPENED 直接 set_pattern——遗留路径 + # (test_runner 等)通过旧 controller.apply_signal_format 写入 + # 信号格式,未经过本设备的 configure。此时 self._state 仍为 + # OPENED,但硬件实际已处于可接收 pattern 状态。 + if self._state == UcdState.CLOSED: + raise UcdNotConnected("UCD 未连接,无法 set_pattern") self._curr_pattern = pattern # 仅本地暂存,真正写硬件在 apply() def apply(self) -> None: with self._lock: - if self._curr_signal is None or self._curr_timing is None: - raise UcdStateError("apply 前必须先 configure") + if self._state == UcdState.CLOSED: + raise UcdNotConnected("UCD 未连接,无法 apply") if self._curr_pattern is None: raise UcdStateError("apply 前必须先 set_pattern") try: @@ -272,12 +274,15 @@ class UCD323Device(IUcdDevice): f"apply 失败: pattern={self._curr_pattern.kind.value}" ) - changed = (self._curr_signal, self._curr_timing) != self._last_applied - self._last_applied = (self._curr_signal, self._curr_timing) + # SignalApplied 事件仅在通过新 API configure 过时发出; + # 遗留路径下 self._curr_signal/_curr_timing 可能为 None。 + if self._curr_signal is not None and self._curr_timing is not None: + changed = (self._curr_signal, self._curr_timing) != self._last_applied + self._last_applied = (self._curr_signal, self._curr_timing) + self._bus.publish( + SignalApplied(self._curr_signal, self._curr_timing, changed) + ) self._state = UcdState.APPLIED - self._bus.publish( - SignalApplied(self._curr_signal, self._curr_timing, changed) - ) self._bus.publish(PatternApplied(self._curr_pattern)) # -- 查询 ---------------------------------------------------- diff --git a/pqAutomationApp.py b/pqAutomationApp.py index d63b60f..7cad30a 100644 --- a/pqAutomationApp.py +++ b/pqAutomationApp.py @@ -9,6 +9,9 @@ import traceback import matplotlib.pyplot as plt from app_version import APP_NAME, APP_VERSION, get_app_title from drivers.UCD323_Function import UCDController +from drivers.ucd_driver import UCD323Device +from app.ucd_domain import EventBus +from app.services.ucd_service import SignalService from app.pq.pq_config import PQConfig from app.pq.pq_result import PQResultStore from app.export import ( @@ -126,7 +129,14 @@ class PQAutomationApp: # 初始化设备连接状态 self.ca = None # CA410色度计 - self.ucd = UCDController() # 信号发生器 + self.ucd = UCDController() # 信号发生器(旧接口,过渡期保留) + + # 新架构:EventBus + 设备抽象 + 服务层。 + # UCD323Device 内部委托 self.ucd,保证零行为变更; + # 新代码统一走 self.signal_service。 + self.event_bus = EventBus() + self.ucd_device = UCD323Device(self.event_bus, self.ucd) + self.signal_service = SignalService(self.ucd_device, self.event_bus) # 初始化测试状态 self.testing = False