mirror of
https://github.com/dptech-corp/Uni-Lab-OS.git
synced 2026-02-04 13:25:13 +00:00
Workbench example, adjust log level, and ci check (#220) * TestLatency Return Value Example & gitignore update * Adjust log level & Add workbench virtual example & Add not action decorator & Add check_mode & * Add CI Check Fix/workstation yb revision (#217) * Revert log change & update registry * Revert opcua client & move electrolyte node Workstation yb merge dev ready 260113 (#216) * feat(bioyond): 添加计算实验设计功能,支持化合物配比和滴定比例参数 * feat(bioyond): 添加测量小瓶功能,支持基本参数配置 * feat(bioyond): 添加测量小瓶配置,支持新设备参数 * feat(bioyond): 更新仓库布局和尺寸,支持竖向排列的测量小瓶和试剂存放堆栈 * feat(bioyond): 优化任务创建流程,确保无论成功与否都清理任务队列以避免重复累积 * feat(bioyond): 添加设置反应器温度功能,支持温度范围和异常处理 * feat(bioyond): 调整反应器位置配置,统一坐标格式 * feat(bioyond): 添加调度器启动功能,支持任务队列执行并处理异常 * feat(bioyond): 优化调度器启动功能,添加异常处理并更新相关配置 * feat(opcua): 增强节点ID解析兼容性和数据类型处理 改进节点ID解析逻辑以支持多种格式,包括字符串和数字标识符 添加数据类型转换处理,确保写入值时类型匹配 优化错误提示信息,便于调试节点连接问题 * feat(registry): 新增后处理站的设备配置文件 添加后处理站的YAML配置文件,包含动作映射、状态类型和设备描述 * 添加调度器启动功能,合并物料参数配置,优化物料参数处理逻辑 * 添加从 Bioyond 系统自动同步工作流序列的功能,并更新相关配置 * fix:兼容 BioyondReactionStation 中 workflow_sequence 被重写为 property * fix:同步工作流序列 * feat: remove commented workflow synchronization from `reaction_station.py`. * 添加时间约束功能及相关配置 * fix:自动更新物料缓存功能,添加物料时更新缓存并在删除时移除缓存项 * fix:在添加物料时处理字符串和字典返回值,确保正确更新缓存 * fix:更新奔曜错误处理报送为物料变更报送,调整日志记录和响应消息 * feat:添加实验报告简化功能,去除冗余信息并保留关键信息 * feat: 添加任务状态事件发布功能,监控并报告任务运行、超时、完成和错误状态 * fix: 修复添加物料时数据格式错误 * Refactor bioyond_dispensing_station and reaction_station_bioyond YAML configurations - Removed redundant action value mappings from bioyond_dispensing_station. - Updated goal properties in bioyond_dispensing_station to use enums for target_stack and other parameters. - Changed data types for end_point and start_point in reaction_station_bioyond to use string enums (Start, End). - Simplified descriptions and updated measurement units from μL to mL where applicable. - Removed unused commands from reaction_station_bioyond to streamline the configuration. * fix:Change the material unit from μL to mL * fix:refresh_material_cache * feat: 动态获取工作流步骤ID,优化工作流配置 * feat: 添加清空服务端所有非核心工作流功能 * fix:修复Bottle类的序列化和反序列化方法 * feat:增强材料缓存更新逻辑,支持处理返回数据中的详细信息 * Add debug log * feat(workstation): update bioyond config migration and coin cell material search logic - Migrate bioyond_cell config to JSON structure and remove global variable dependencies - Implement material search confirmation dialog auto-handling - Add documentation: 20260113_物料搜寻确认弹窗自动处理功能.md and 20260113_配置迁移修改总结.md * Refactor module paths for Bioyond devices in YAML configuration files - Updated the module path for BioyondDispensingStation in bioyond_dispensing_station.yaml to reflect the new directory structure. - Updated the module path for BioyondReactionStation and BioyondReactor in reaction_station_bioyond.yaml to align with the revised organization of the codebase. * fix: WareHouse 的不可哈希类型错误,优化父节点去重逻辑 * refactor: Move config from module to instance initialization * fix: 修正 reaction_station 目录名拼写错误 * feat: Integrate material search logic and cleanup deprecated files - Update coin_cell_assembly.py with material search dialog handling - Update YB_warehouses.py with latest warehouse configurations - Remove outdated documentation and test data files * Refactor: Use instance attributes for action names and workflow step IDs * refactor: Split tipbox storage into left and right warehouses * refactor: Merge tipbox storage left and right into single warehouse --------- Co-authored-by: ZiWei <131428629+ZiWei09@users.noreply.github.com> Co-authored-by: Andy6M <xieqiming1132@qq.com> fix: WareHouse 的不可哈希类型错误,优化父节点去重逻辑 fix parent_uuid fetch when bind_parent_id == node_name 物料更新也是用父节点进行报送 Add None conversion for tube rack etc. Add set_liquid example. Add create_resource and test_resource example. Add restart. Temp allow action message. Add no_update_feedback option. Create session_id by edge. bump version to 0.10.15 temp cancel update req
312 lines
12 KiB
Python
312 lines
12 KiB
Python
"""
|
||
环境检查模块
|
||
用于检查并自动安装 UniLabOS 运行所需的 Python 包
|
||
"""
|
||
|
||
import argparse
|
||
import importlib
|
||
import locale
|
||
import subprocess
|
||
import sys
|
||
|
||
from unilabos.utils.banner_print import print_status
|
||
|
||
|
||
class EnvironmentChecker:
|
||
"""环境检查器"""
|
||
|
||
def __init__(self):
|
||
# 定义必需的包及其安装名称的映射
|
||
self.required_packages = {
|
||
# 包导入名 : pip安装名
|
||
# "pymodbus.framer.FramerType": "pymodbus==3.9.2",
|
||
"websockets": "websockets",
|
||
"msgcenterpy": "msgcenterpy",
|
||
"opentrons_shared_data": "opentrons_shared_data",
|
||
"typing_extensions": "typing_extensions",
|
||
"crcmod": "crcmod-plus",
|
||
}
|
||
|
||
# 特殊安装包(需要特殊处理的包)
|
||
self.special_packages = {"pylabrobot": "git+https://github.com/Xuwznln/pylabrobot.git"}
|
||
|
||
# 包版本要求(包名: 最低版本)
|
||
self.version_requirements = {
|
||
"msgcenterpy": "0.1.5", # msgcenterpy 最低版本要求
|
||
}
|
||
|
||
self.missing_packages = []
|
||
self.failed_installs = []
|
||
self.packages_need_upgrade = []
|
||
|
||
# 检测系统语言
|
||
self.is_chinese = self._is_chinese_locale()
|
||
|
||
def _is_chinese_locale(self) -> bool:
|
||
"""检测系统是否为中文环境"""
|
||
try:
|
||
lang = locale.getdefaultlocale()[0]
|
||
if lang and ("zh" in lang.lower() or "chinese" in lang.lower()):
|
||
return True
|
||
except Exception:
|
||
pass
|
||
return False
|
||
|
||
def check_package_installed(self, package_name: str) -> bool:
|
||
"""检查包是否已安装"""
|
||
try:
|
||
importlib.import_module(package_name)
|
||
return True
|
||
except ImportError:
|
||
return False
|
||
|
||
def get_package_version(self, package_name: str) -> str | None:
|
||
"""获取已安装包的版本"""
|
||
try:
|
||
module = importlib.import_module(package_name)
|
||
return getattr(module, "__version__", None)
|
||
except (ImportError, AttributeError):
|
||
return None
|
||
|
||
def compare_version(self, current: str, required: str) -> bool:
|
||
"""
|
||
比较版本号
|
||
Returns:
|
||
True: current >= required
|
||
False: current < required
|
||
"""
|
||
try:
|
||
current_parts = [int(x) for x in current.split(".")]
|
||
required_parts = [int(x) for x in required.split(".")]
|
||
|
||
# 补齐长度
|
||
max_len = max(len(current_parts), len(required_parts))
|
||
current_parts.extend([0] * (max_len - len(current_parts)))
|
||
required_parts.extend([0] * (max_len - len(required_parts)))
|
||
|
||
return current_parts >= required_parts
|
||
except Exception:
|
||
return True # 如果无法比较,假设版本满足要求
|
||
|
||
def install_package(self, package_name: str, pip_name: str, upgrade: bool = False) -> bool:
|
||
"""安装包"""
|
||
try:
|
||
action = "升级" if upgrade else "安装"
|
||
print_status(f"正在{action} {package_name} ({pip_name})...", "info")
|
||
|
||
# 构建安装命令
|
||
cmd = [sys.executable, "-m", "pip", "install"]
|
||
|
||
# 如果是升级操作,添加 --upgrade 参数
|
||
if upgrade:
|
||
cmd.append("--upgrade")
|
||
|
||
cmd.append(pip_name)
|
||
|
||
# 如果是中文环境,使用清华镜像源
|
||
if self.is_chinese:
|
||
cmd.extend(["-i", "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple"])
|
||
|
||
# 执行安装
|
||
result = subprocess.run(cmd, capture_output=True, text=True, timeout=300) # 5分钟超时
|
||
|
||
if result.returncode == 0:
|
||
print_status(f"✓ {package_name} {action}成功", "success")
|
||
return True
|
||
else:
|
||
print_status(f"× {package_name} {action}失败: {result.stderr}", "error")
|
||
return False
|
||
|
||
except subprocess.TimeoutExpired:
|
||
print_status(f"× {package_name} {action}超时", "error")
|
||
return False
|
||
except Exception as e:
|
||
print_status(f"× {package_name} {action}异常: {str(e)}", "error")
|
||
return False
|
||
|
||
def upgrade_package(self, package_name: str, pip_name: str) -> bool:
|
||
"""升级包"""
|
||
return self.install_package(package_name, pip_name, upgrade=True)
|
||
|
||
def check_all_packages(self) -> bool:
|
||
"""检查所有必需的包"""
|
||
print_status("开始检查环境依赖...", "info")
|
||
|
||
# 检查常规包
|
||
for import_name, pip_name in self.required_packages.items():
|
||
if not self.check_package_installed(import_name):
|
||
self.missing_packages.append((import_name, pip_name))
|
||
else:
|
||
# 检查版本要求
|
||
if import_name in self.version_requirements:
|
||
current_version = self.get_package_version(import_name)
|
||
required_version = self.version_requirements[import_name]
|
||
|
||
if current_version:
|
||
if not self.compare_version(current_version, required_version):
|
||
print_status(
|
||
f"{import_name} 版本过低 (当前: {current_version}, 需要: >={required_version})",
|
||
"warning",
|
||
)
|
||
self.packages_need_upgrade.append((import_name, pip_name))
|
||
|
||
# 检查特殊包
|
||
for package_name, install_url in self.special_packages.items():
|
||
if not self.check_package_installed(package_name):
|
||
self.missing_packages.append((package_name, install_url))
|
||
|
||
all_ok = not self.missing_packages and not self.packages_need_upgrade
|
||
|
||
if all_ok:
|
||
print_status("✓ 所有依赖包检查完成,环境正常", "success")
|
||
return True
|
||
|
||
if self.missing_packages:
|
||
print_status(f"发现 {len(self.missing_packages)} 个缺失的包", "warning")
|
||
if self.packages_need_upgrade:
|
||
print_status(f"发现 {len(self.packages_need_upgrade)} 个需要升级的包", "warning")
|
||
|
||
return False
|
||
|
||
def install_missing_packages(self, auto_install: bool = True) -> bool:
|
||
"""安装缺失的包"""
|
||
if not self.missing_packages and not self.packages_need_upgrade:
|
||
return True
|
||
|
||
if not auto_install:
|
||
if self.missing_packages:
|
||
print_status("缺失以下包:", "warning")
|
||
for import_name, pip_name in self.missing_packages:
|
||
print_status(f" - {import_name} (pip install {pip_name})", "warning")
|
||
if self.packages_need_upgrade:
|
||
print_status("需要升级以下包:", "warning")
|
||
for import_name, pip_name in self.packages_need_upgrade:
|
||
print_status(f" - {import_name} (pip install --upgrade {pip_name})", "warning")
|
||
return False
|
||
|
||
# 安装缺失的包
|
||
if self.missing_packages:
|
||
print_status(f"开始自动安装 {len(self.missing_packages)} 个缺失的包...", "info")
|
||
|
||
success_count = 0
|
||
for import_name, pip_name in self.missing_packages:
|
||
if self.install_package(import_name, pip_name):
|
||
success_count += 1
|
||
else:
|
||
self.failed_installs.append((import_name, pip_name))
|
||
|
||
print_status(f"✓ 成功安装 {success_count}/{len(self.missing_packages)} 个包", "success")
|
||
|
||
# 升级需要更新的包
|
||
if self.packages_need_upgrade:
|
||
print_status(f"开始自动升级 {len(self.packages_need_upgrade)} 个包...", "info")
|
||
|
||
upgrade_success_count = 0
|
||
for import_name, pip_name in self.packages_need_upgrade:
|
||
if self.upgrade_package(import_name, pip_name):
|
||
upgrade_success_count += 1
|
||
else:
|
||
self.failed_installs.append((import_name, pip_name))
|
||
|
||
print_status(f"✓ 成功升级 {upgrade_success_count}/{len(self.packages_need_upgrade)} 个包", "success")
|
||
|
||
if self.failed_installs:
|
||
print_status(f"有 {len(self.failed_installs)} 个包操作失败:", "error")
|
||
for import_name, pip_name in self.failed_installs:
|
||
print_status(f" - {import_name} ({pip_name})", "error")
|
||
return False
|
||
|
||
return True
|
||
|
||
def verify_installation(self) -> bool:
|
||
"""验证安装结果"""
|
||
if not self.missing_packages and not self.packages_need_upgrade:
|
||
return True
|
||
|
||
print_status("验证安装结果...", "info")
|
||
|
||
failed_verification = []
|
||
|
||
# 验证新安装的包
|
||
for import_name, pip_name in self.missing_packages:
|
||
if not self.check_package_installed(import_name):
|
||
failed_verification.append((import_name, pip_name))
|
||
|
||
# 验证升级的包
|
||
for import_name, pip_name in self.packages_need_upgrade:
|
||
if not self.check_package_installed(import_name):
|
||
failed_verification.append((import_name, pip_name))
|
||
elif import_name in self.version_requirements:
|
||
current_version = self.get_package_version(import_name)
|
||
required_version = self.version_requirements[import_name]
|
||
if current_version and not self.compare_version(current_version, required_version):
|
||
failed_verification.append((import_name, pip_name))
|
||
print_status(
|
||
f" {import_name} 版本仍然过低 (当前: {current_version}, 需要: >={required_version})",
|
||
"error",
|
||
)
|
||
|
||
if failed_verification:
|
||
print_status(f"有 {len(failed_verification)} 个包验证失败:", "error")
|
||
for import_name, pip_name in failed_verification:
|
||
print_status(f" - {import_name}", "error")
|
||
return False
|
||
|
||
print_status("✓ 所有包验证通过", "success")
|
||
return True
|
||
|
||
|
||
def check_environment(auto_install: bool = True, show_details: bool = True) -> bool:
|
||
"""
|
||
检查环境并自动安装缺失的包
|
||
|
||
Args:
|
||
auto_install: 是否自动安装缺失的包
|
||
show_details: 是否显示详细信息
|
||
|
||
Returns:
|
||
bool: 环境检查是否通过
|
||
"""
|
||
checker = EnvironmentChecker()
|
||
|
||
# 检查包
|
||
if checker.check_all_packages():
|
||
return True
|
||
|
||
# 安装缺失的包
|
||
if not checker.install_missing_packages(auto_install):
|
||
if show_details:
|
||
print_status("请手动安装缺失的包后重新启动程序", "error")
|
||
return False
|
||
|
||
# 验证安装
|
||
if not checker.verify_installation():
|
||
if show_details:
|
||
print_status("安装验证失败,请检查网络连接或手动安装", "error")
|
||
return False
|
||
|
||
return True
|
||
|
||
|
||
if __name__ == "__main__":
|
||
# 命令行参数解析
|
||
parser = argparse.ArgumentParser(description="UniLabOS 环境依赖检查工具")
|
||
parser.add_argument("--no-auto-install", action="store_true", help="仅检查环境,不自动安装缺失的包")
|
||
parser.add_argument("--silent", action="store_true", help="静默模式,不显示详细信息")
|
||
|
||
args = parser.parse_args()
|
||
|
||
# 执行环境检查
|
||
auto_install = not args.no_auto_install
|
||
show_details = not args.silent
|
||
|
||
success = check_environment(auto_install=auto_install, show_details=show_details)
|
||
|
||
if not success:
|
||
if show_details:
|
||
print_status("环境检查失败", "error")
|
||
sys.exit(1)
|
||
else:
|
||
if show_details:
|
||
print_status("环境检查完成", "success")
|