Files
Uni-Lab-OS/unilabos/devices/platereader/NivoDriver.py
Junhan Chang c78ac482d8 Initial commit
2025-04-17 15:19:47 +08:00

296 lines
12 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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