import time import traceback from typing import Optional import requests from pywinauto.application import WindowSpecification from pywinauto.controls.uiawrapper import UIAWrapper from pywinauto_recorder import UIApplication from pywinauto_recorder.player import UIPath, click, focus_on_application, exists, find, FailedSearch from unilabos.device_comms import universal_driver as ud from unilabos.device_comms.universal_driver import UniversalDriver, SingleRunningExecutor from unilabos.utils.pywinauto_util import connect_application, get_process_pid_by_name, \ get_ui_path_with_window_specification class NivoDriver(UniversalDriver): # 初始指定 _device_ip: str = None # 软件状态检测 _software_enabled: bool = False _software_pid: int = None _http_service_available: bool = False # 初始化状态 _is_initialized: bool = False # 任务是否成功 _success: bool = False # 运行状态 _is_executing_run: bool = False _executing_ui_path: Optional[UIPath] = None _executing_index: Optional[int] = None _executing_status: Optional[str] = None _total_tasks: Optional[list[str]] = None _guide_app: Optional[UIApplication] = None @property def executing_status(self) -> str: if self._total_tasks is None: return f"无任务" if self._executing_index is None: return f"等待任务开始,总计{len(self._total_tasks)}个".encode('utf-8').decode('utf-8') else: return f"正在执行第{self._executing_index + 1}/{len(self._total_tasks)}个任务,当前状态:{self._executing_status}".encode('utf-8').decode('utf-8') @property def device_ip(self) -> str: return self._device_ip @property def success(self) -> bool: return self._success @property def status(self) -> str: return f"Software: {self._software_enabled}, HTTP: {self._http_service_available} Initialized: {self._is_initialized} Executing: {self._is_executing_run}" def set_device_addr(self, device_ip_str): self._device_ip = device_ip_str print(f"Set device IP to: {self.device_ip}") def run_instrument(self): if not self._is_initialized: print("Instrument is not initialized") self._success = False return False def post_func(res, _): self._success = res if not res: self._is_executing_run = False ins: SingleRunningExecutor = SingleRunningExecutor.get_instance(self.execute_run_instrument, post_func) if not ins.is_ended and ins.is_started: print("Function is running") self._success = False return False elif not ins.is_started: print("Function started") ins.start() # 开始执行 else: print("Function reset and started") ins.reset() ins.start() def execute_run_instrument(self): process_found, process_pid = get_process_pid_by_name("Guide.exe", min_memory_mb=20) if not process_found: uiapp = connect_application(process=self._software_pid) focus_on_application(uiapp) ui_window: WindowSpecification = uiapp.app.top_window() button: WindowSpecification = ui_window.child_window(title="xtpBarTop", class_name="XTPDockBar").child_window( title="Standard", class_name="XTPToolBar").child_window(title="Protocol", control_type="Button") click(button.wrapper_object()) for _ in range(5): time.sleep(1) process_found, process_pid = get_process_pid_by_name("Guide.exe", min_memory_mb=20) if process_found: break if not process_found: print("Guide.exe not found") self._success = False return False uiapp = connect_application(process=process_pid) self._guide_app = uiapp focus_on_application(uiapp) wrapper_object = uiapp.app.top_window().wrapper_object() ui_path = get_ui_path_with_window_specification(wrapper_object) self._executing_ui_path = ui_path with ui_path: try: click(u"||Custom->||RadioButton-> Run||Text-> Run||Text") except FailedSearch as e: print(f"未找到Run按钮,可能已经在执行了") with UIPath(u"WAA.Guide.Guide.RunControlViewModel||Custom->||Custom"): click(u"Start||Button") with UIPath(u"WAA.Guide.Guide.RunControlViewModel||Custom->||Custom->||Group->WAA.Guide.Guide.StartupControlViewModel||Custom->||Custom->Ok||Button"): while self._executing_index is None or self._executing_index == 0: if exists(None, timeout=2): click(u"Ok||Text") print("Run Init Success!") self._is_executing_run = True return True else: print("Wait for Ok button") def check_execute_run_status(self): if not self._is_executing_run: return False if self._executing_ui_path is None: return False total_tasks = [] executing_index = 0 executing_status = "" procedure_name_found = False with self._executing_ui_path: with UIPath(u"WAA.Guide.Guide.RunControlViewModel||Custom->||Custom"): with UIPath("Progress||Group->||DataGrid"): wrappered_object: UIAWrapper = find(timeout=0.5) # BUG: 在查找的时候会触发全局锁,建议还是使用Process来检测 for custom_wrapper in wrappered_object.children(): if len(custom_wrapper.children()) == 1: each_custom_wrapper = custom_wrapper.children()[0] if len(each_custom_wrapper.children()) == 2: if not procedure_name_found: procedure_name_found = True continue task_wrapper = each_custom_wrapper.children()[0] total_tasks.append(task_wrapper.window_text()) status_wrapper = each_custom_wrapper.children()[1] status = status_wrapper.window_text() if len(status) > 0: executing_index = len(total_tasks) - 1 executing_status = status try: if self._guide_app is not None: wrapper_object = self._guide_app.app.top_window().wrapper_object() ui_path = get_ui_path_with_window_specification(wrapper_object) with ui_path: with UIPath("OK||Button"): btn = find(timeout=1) if btn is not None: btn.set_focus() click(btn, timeout=1) self._is_executing_run = False print("运行完成!") except: pass self._executing_index = executing_index self._executing_status = executing_status self._total_tasks = total_tasks return True def initialize_instrument(self, force=False): if not self._software_enabled: print("Software is not opened") self._success = False return if not self._http_service_available: print("HTTP Server Not Available") self._success = False return if self._is_initialized and not force: print("Already Initialized") self._success = True return True ins: SingleRunningExecutor = SingleRunningExecutor.get_instance(self.execute_initialize, lambda res, _: setattr(self, '_success', res)) if not ins.is_ended and ins.is_started: print("Initialize is running") self._success = False return False elif not ins.is_started: print("Initialize started") ins.start() else: # 可能外面is_initialized被设置为False,又进来重新初始化了 print("Initialize reset and started") ins.reset() ins.start() return True def execute_initialize(self, process=None) -> bool: if process is None: process = self._software_pid try: uiapp = connect_application(process=process) ui_window: WindowSpecification = uiapp.app.top_window() button = ui_window.child_window(title="xtpBarTop", class_name="XTPDockBar").child_window( title="Standard", class_name="XTPToolBar").child_window(title="Initialize Instrument", control_type="Button") focus_on_application(uiapp) click(button.wrapper_object()) with get_ui_path_with_window_specification(ui_window): with UIPath("Regex: (Initializing|Resetting|Perking).*||Window"): # 检测窗口是否存在 for i in range(3): try: initializing_windows = exists(None, timeout=2) break except: pass print("window has recovered", initializing_windows) time.sleep(5) # another wait self._is_initialized = True return True except Exception as e: print("An error occurred during initialization:") traceback.print_exc() return False def __init__(self): self._device_ip = "192.168.0.2" # 启动所有监控器 self.checkers = [ ProcessChecker(self, 1), HttpServiceChecker(self, 3), RunStatusChecker(self, 1), OkButtonChecker(self, 2) # 添加新的Checker ] for checker in self.checkers: checker.start_monitoring() class DriverChecker(ud.DriverChecker): driver: NivoDriver class ProcessChecker(DriverChecker): def check(self): process_found, process_pid = get_process_pid_by_name("JANUS.exe", min_memory_mb=20) self.driver._software_pid = process_pid self.driver._software_enabled = process_found if not process_found: self.driver._is_initialized = False class HttpServiceChecker(DriverChecker): def check(self): http_service_available = False if self.driver.device_ip: try: response = requests.get(f"http://{self.driver.device_ip}", timeout=5) http_service_available = response.status_code == 200 except requests.RequestException: pass self.driver._http_service_available = http_service_available class RunStatusChecker(DriverChecker): def check(self): process_found, process_pid = get_process_pid_by_name("Guide.exe", min_memory_mb=20) if not process_found: self.driver._is_executing_run = False return self.driver.check_execute_run_status() class OkButtonChecker(DriverChecker): def check(self): if not self.driver._is_executing_run or self.driver._guide_app is None: return # uiapp = connect_application(process=11276) # self.driver._guide_app = uiapp try: ui_window: UIAWrapper = self.driver._guide_app.app.top_window() btn: WindowSpecification = ui_window.child_window(title="OK", auto_id="2", control_type="Button") if btn.exists(2): click(btn.wrapper_object()) self.driver._is_executing_run = False print("运行完成!") except Exception as e: # traceback.print_exc() pass # 示例用法 if __name__ == "__main__": driver = NivoDriver() driver.set_device_addr("192.168.0.2") # 设置设备 IP 地址 driver._is_executing_run = True