mirror of
https://github.com/dptech-corp/Uni-Lab-OS.git
synced 2026-02-04 13:25:13 +00:00
Merge branch 'dev' into prcix9320
This commit is contained in:
@@ -1 +1 @@
|
||||
__version__ = "0.10.15"
|
||||
__version__ = "0.10.17"
|
||||
|
||||
@@ -7,7 +7,6 @@ import sys
|
||||
import threading
|
||||
import time
|
||||
from typing import Dict, Any, List
|
||||
|
||||
import networkx as nx
|
||||
import yaml
|
||||
|
||||
@@ -17,9 +16,9 @@ unilabos_dir = os.path.dirname(os.path.dirname(current_dir))
|
||||
if unilabos_dir not in sys.path:
|
||||
sys.path.append(unilabos_dir)
|
||||
|
||||
from unilabos.app.utils import cleanup_for_restart
|
||||
from unilabos.utils.banner_print import print_status, print_unilab_banner
|
||||
from unilabos.config.config import load_config, BasicConfig, HTTPConfig
|
||||
from unilabos.app.utils import cleanup_for_restart
|
||||
|
||||
# Global restart flags (used by ws_client and web/server)
|
||||
_restart_requested: bool = False
|
||||
@@ -161,6 +160,12 @@ def parse_args():
|
||||
default=False,
|
||||
help="Complete registry information",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--check_mode",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help="Run in check mode for CI: validates registry imports and ensures no file changes",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--no_update_feedback",
|
||||
action="store_true",
|
||||
@@ -211,7 +216,10 @@ def main():
|
||||
args_dict = vars(args)
|
||||
|
||||
# 环境检查 - 检查并自动安装必需的包 (可选)
|
||||
if not args_dict.get("skip_env_check", False):
|
||||
skip_env_check = args_dict.get("skip_env_check", False)
|
||||
check_mode = args_dict.get("check_mode", False)
|
||||
|
||||
if not skip_env_check:
|
||||
from unilabos.utils.environment_check import check_environment
|
||||
|
||||
if not check_environment(auto_install=True):
|
||||
@@ -222,7 +230,21 @@ def main():
|
||||
|
||||
# 加载配置文件,优先加载config,然后从env读取
|
||||
config_path = args_dict.get("config")
|
||||
if os.getcwd().endswith("unilabos_data"):
|
||||
|
||||
if check_mode:
|
||||
args_dict["working_dir"] = os.path.abspath(os.getcwd())
|
||||
# 当 skip_env_check 时,默认使用当前目录作为 working_dir
|
||||
if skip_env_check and not args_dict.get("working_dir") and not config_path:
|
||||
working_dir = os.path.abspath(os.getcwd())
|
||||
print_status(f"跳过环境检查模式:使用当前目录作为工作目录 {working_dir}", "info")
|
||||
# 检查当前目录是否有 local_config.py
|
||||
local_config_in_cwd = os.path.join(working_dir, "local_config.py")
|
||||
if os.path.exists(local_config_in_cwd):
|
||||
config_path = local_config_in_cwd
|
||||
print_status(f"发现本地配置文件: {config_path}", "info")
|
||||
else:
|
||||
print_status(f"未指定config路径,可通过 --config 传入 local_config.py 文件路径", "info")
|
||||
elif os.getcwd().endswith("unilabos_data"):
|
||||
working_dir = os.path.abspath(os.getcwd())
|
||||
else:
|
||||
working_dir = os.path.abspath(os.path.join(os.getcwd(), "unilabos_data"))
|
||||
@@ -241,7 +263,7 @@ def main():
|
||||
working_dir = os.path.dirname(config_path)
|
||||
elif os.path.exists(working_dir) and os.path.exists(os.path.join(working_dir, "local_config.py")):
|
||||
config_path = os.path.join(working_dir, "local_config.py")
|
||||
elif not config_path and (
|
||||
elif not skip_env_check and not config_path and (
|
||||
not os.path.exists(working_dir) or not os.path.exists(os.path.join(working_dir, "local_config.py"))
|
||||
):
|
||||
print_status(f"未指定config路径,可通过 --config 传入 local_config.py 文件路径", "info")
|
||||
@@ -255,9 +277,11 @@ def main():
|
||||
print_status(f"已创建 local_config.py 路径: {config_path}", "info")
|
||||
else:
|
||||
os._exit(1)
|
||||
# 加载配置文件
|
||||
|
||||
# 加载配置文件 (check_mode 跳过)
|
||||
print_status(f"当前工作目录为 {working_dir}", "info")
|
||||
load_config_from_file(config_path)
|
||||
if not check_mode:
|
||||
load_config_from_file(config_path)
|
||||
|
||||
# 根据配置重新设置日志级别
|
||||
from unilabos.utils.log import configure_logger, logger
|
||||
@@ -313,6 +337,7 @@ def main():
|
||||
machine_name = "".join([c if c.isalnum() or c == "_" else "_" for c in machine_name])
|
||||
BasicConfig.machine_name = machine_name
|
||||
BasicConfig.vis_2d_enable = args_dict["2d_vis"]
|
||||
BasicConfig.check_mode = check_mode
|
||||
|
||||
from unilabos.resources.graphio import (
|
||||
read_node_link_json,
|
||||
@@ -331,10 +356,14 @@ def main():
|
||||
# 显示启动横幅
|
||||
print_unilab_banner(args_dict)
|
||||
|
||||
# 注册表
|
||||
lab_registry = build_registry(
|
||||
args_dict["registry_path"], args_dict.get("complete_registry", False), BasicConfig.upload_registry
|
||||
)
|
||||
# 注册表 - check_mode 时强制启用 complete_registry
|
||||
complete_registry = args_dict.get("complete_registry", False) or check_mode
|
||||
lab_registry = build_registry(args_dict["registry_path"], complete_registry, BasicConfig.upload_registry)
|
||||
|
||||
# Check mode: complete_registry 完成后直接退出,git diff 检测由 CI workflow 执行
|
||||
if check_mode:
|
||||
print_status("Check mode: complete_registry 完成,退出", "info")
|
||||
os._exit(0)
|
||||
|
||||
if BasicConfig.upload_registry:
|
||||
# 设备注册到服务端 - 需要 ak 和 sk
|
||||
|
||||
@@ -4,8 +4,40 @@ UniLabOS 应用工具函数
|
||||
提供清理、重启等工具函数
|
||||
"""
|
||||
|
||||
import gc
|
||||
import glob
|
||||
import os
|
||||
import shutil
|
||||
import sys
|
||||
|
||||
|
||||
def patch_rclpy_dll_windows():
|
||||
"""在 Windows + conda 环境下为 rclpy 打 DLL 加载补丁"""
|
||||
if sys.platform != "win32" or not os.environ.get("CONDA_PREFIX"):
|
||||
return
|
||||
try:
|
||||
import rclpy
|
||||
|
||||
return
|
||||
except ImportError as e:
|
||||
if not str(e).startswith("DLL load failed"):
|
||||
return
|
||||
cp = os.environ["CONDA_PREFIX"]
|
||||
impl = os.path.join(cp, "Lib", "site-packages", "rclpy", "impl", "implementation_singleton.py")
|
||||
pyd = glob.glob(os.path.join(cp, "Lib", "site-packages", "rclpy", "_rclpy_pybind11*.pyd"))
|
||||
if not os.path.exists(impl) or not pyd:
|
||||
return
|
||||
with open(impl, "r", encoding="utf-8") as f:
|
||||
content = f.read()
|
||||
lib_bin = os.path.join(cp, "Library", "bin").replace("\\", "/")
|
||||
patch = f'# UniLabOS DLL Patch\nimport os,ctypes\nos.add_dll_directory("{lib_bin}") if hasattr(os,"add_dll_directory") else None\ntry: ctypes.CDLL("{pyd[0].replace(chr(92),"/")}")\nexcept: pass\n# End Patch\n'
|
||||
shutil.copy2(impl, impl + ".bak")
|
||||
with open(impl, "w", encoding="utf-8") as f:
|
||||
f.write(patch + content)
|
||||
|
||||
|
||||
patch_rclpy_dll_windows()
|
||||
|
||||
import gc
|
||||
import threading
|
||||
import time
|
||||
|
||||
|
||||
0
unilabos/devices/Qone_nmr/__init__.py
Normal file
0
unilabos/devices/Qone_nmr/__init__.py
Normal file
0
unilabos/devices/xrd_d7mate/__init__.py
Normal file
0
unilabos/devices/xrd_d7mate/__init__.py
Normal file
0
unilabos/devices/zhida_hplc/__init__.py
Normal file
0
unilabos/devices/zhida_hplc/__init__.py
Normal file
@@ -9752,21 +9752,21 @@ liquid_handler.prcxi:
|
||||
- 0
|
||||
handles:
|
||||
input:
|
||||
- data_key: liquid
|
||||
- data_key: sources
|
||||
data_source: handle
|
||||
data_type: resource
|
||||
handler_key: sources
|
||||
label: sources
|
||||
- data_key: liquid
|
||||
data_source: executor
|
||||
handler_key: sources_identifier
|
||||
label: 待移动液体
|
||||
- data_key: targets
|
||||
data_source: handle
|
||||
data_type: resource
|
||||
handler_key: targets
|
||||
label: targets
|
||||
- data_key: liquid
|
||||
data_source: executor
|
||||
handler_key: targets_identifier
|
||||
label: 转移目标
|
||||
- data_key: tip_rack
|
||||
data_source: handle
|
||||
data_type: resource
|
||||
handler_key: tip_rack
|
||||
label: tip_rack
|
||||
handler_key: tip_rack_identifier
|
||||
label: 墙头盒
|
||||
output:
|
||||
- data_key: liquid
|
||||
data_source: handle
|
||||
|
||||
@@ -5792,3 +5792,381 @@ virtual_vacuum_pump:
|
||||
- status
|
||||
type: object
|
||||
version: 1.0.0
|
||||
virtual_workbench:
|
||||
category:
|
||||
- virtual_device
|
||||
class:
|
||||
action_value_mappings:
|
||||
auto-move_to_heating_station:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default:
|
||||
material_number: null
|
||||
handles:
|
||||
input:
|
||||
- data_key: material_number
|
||||
data_source: handle
|
||||
data_type: workbench_material
|
||||
handler_key: material_input
|
||||
label: 物料编号
|
||||
output:
|
||||
- data_key: station_id
|
||||
data_source: executor
|
||||
data_type: workbench_station
|
||||
handler_key: heating_station_output
|
||||
label: 加热台ID
|
||||
- data_key: material_number
|
||||
data_source: executor
|
||||
data_type: workbench_material
|
||||
handler_key: material_number_output
|
||||
label: 物料编号
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: 将物料从An位置移动到空闲加热台,返回分配的加热台ID
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
material_number:
|
||||
description: 物料编号,1-5,物料ID自动生成为A{n}
|
||||
type: integer
|
||||
required:
|
||||
- material_number
|
||||
type: object
|
||||
result:
|
||||
description: move_to_heating_station 返回类型
|
||||
properties:
|
||||
material_id:
|
||||
title: Material Id
|
||||
type: string
|
||||
material_number:
|
||||
title: Material Number
|
||||
type: integer
|
||||
message:
|
||||
title: Message
|
||||
type: string
|
||||
station_id:
|
||||
description: 分配的加热台ID
|
||||
title: Station Id
|
||||
type: integer
|
||||
success:
|
||||
title: Success
|
||||
type: boolean
|
||||
required:
|
||||
- success
|
||||
- station_id
|
||||
- material_id
|
||||
- material_number
|
||||
- message
|
||||
title: MoveToHeatingStationResult
|
||||
type: object
|
||||
required:
|
||||
- goal
|
||||
title: move_to_heating_station参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-move_to_output:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default:
|
||||
material_number: null
|
||||
station_id: null
|
||||
handles:
|
||||
input:
|
||||
- data_key: station_id
|
||||
data_source: handle
|
||||
data_type: workbench_station
|
||||
handler_key: output_station_input
|
||||
label: 加热台ID
|
||||
- data_key: material_number
|
||||
data_source: handle
|
||||
data_type: workbench_material
|
||||
handler_key: output_material_input
|
||||
label: 物料编号
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: 将物料从加热台移动到输出位置Cn
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
material_number:
|
||||
description: 物料编号,用于确定输出位置Cn
|
||||
type: integer
|
||||
station_id:
|
||||
description: 加热台ID,1-3,从上一节点传入
|
||||
type: integer
|
||||
required:
|
||||
- station_id
|
||||
- material_number
|
||||
type: object
|
||||
result:
|
||||
description: move_to_output 返回类型
|
||||
properties:
|
||||
material_id:
|
||||
title: Material Id
|
||||
type: string
|
||||
station_id:
|
||||
title: Station Id
|
||||
type: integer
|
||||
success:
|
||||
title: Success
|
||||
type: boolean
|
||||
required:
|
||||
- success
|
||||
- station_id
|
||||
- material_id
|
||||
title: MoveToOutputResult
|
||||
type: object
|
||||
required:
|
||||
- goal
|
||||
title: move_to_output参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-prepare_materials:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default:
|
||||
count: 5
|
||||
handles:
|
||||
output:
|
||||
- data_key: material_1
|
||||
data_source: executor
|
||||
data_type: workbench_material
|
||||
handler_key: channel_1
|
||||
label: 实验1
|
||||
- data_key: material_2
|
||||
data_source: executor
|
||||
data_type: workbench_material
|
||||
handler_key: channel_2
|
||||
label: 实验2
|
||||
- data_key: material_3
|
||||
data_source: executor
|
||||
data_type: workbench_material
|
||||
handler_key: channel_3
|
||||
label: 实验3
|
||||
- data_key: material_4
|
||||
data_source: executor
|
||||
data_type: workbench_material
|
||||
handler_key: channel_4
|
||||
label: 实验4
|
||||
- data_key: material_5
|
||||
data_source: executor
|
||||
data_type: workbench_material
|
||||
handler_key: channel_5
|
||||
label: 实验5
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: 批量准备物料 - 虚拟起始节点,生成A1-A5物料,输出5个handle供后续节点使用
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
count:
|
||||
default: 5
|
||||
description: 待生成的物料数量,默认5 (生成 A1-A5)
|
||||
type: integer
|
||||
required: []
|
||||
type: object
|
||||
result:
|
||||
description: prepare_materials 返回类型 - 批量准备物料
|
||||
properties:
|
||||
count:
|
||||
title: Count
|
||||
type: integer
|
||||
material_1:
|
||||
title: Material 1
|
||||
type: integer
|
||||
material_2:
|
||||
title: Material 2
|
||||
type: integer
|
||||
material_3:
|
||||
title: Material 3
|
||||
type: integer
|
||||
material_4:
|
||||
title: Material 4
|
||||
type: integer
|
||||
material_5:
|
||||
title: Material 5
|
||||
type: integer
|
||||
message:
|
||||
title: Message
|
||||
type: string
|
||||
success:
|
||||
title: Success
|
||||
type: boolean
|
||||
required:
|
||||
- success
|
||||
- count
|
||||
- material_1
|
||||
- material_2
|
||||
- material_3
|
||||
- material_4
|
||||
- material_5
|
||||
- message
|
||||
title: PrepareMaterialsResult
|
||||
type: object
|
||||
required:
|
||||
- goal
|
||||
title: prepare_materials参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-start_heating:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default:
|
||||
material_number: null
|
||||
station_id: null
|
||||
handles:
|
||||
input:
|
||||
- data_key: station_id
|
||||
data_source: handle
|
||||
data_type: workbench_station
|
||||
handler_key: station_id_input
|
||||
label: 加热台ID
|
||||
- data_key: material_number
|
||||
data_source: handle
|
||||
data_type: workbench_material
|
||||
handler_key: material_number_input
|
||||
label: 物料编号
|
||||
output:
|
||||
- data_key: station_id
|
||||
data_source: executor
|
||||
data_type: workbench_station
|
||||
handler_key: heating_done_station
|
||||
label: 加热完成-加热台ID
|
||||
- data_key: material_number
|
||||
data_source: executor
|
||||
data_type: workbench_material
|
||||
handler_key: heating_done_material
|
||||
label: 加热完成-物料编号
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: 启动指定加热台的加热程序
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
material_number:
|
||||
description: 物料编号,从上一节点传入
|
||||
type: integer
|
||||
station_id:
|
||||
description: 加热台ID,1-3,从上一节点传入
|
||||
type: integer
|
||||
required:
|
||||
- station_id
|
||||
- material_number
|
||||
type: object
|
||||
result:
|
||||
description: start_heating 返回类型
|
||||
properties:
|
||||
material_id:
|
||||
title: Material Id
|
||||
type: string
|
||||
material_number:
|
||||
title: Material Number
|
||||
type: integer
|
||||
message:
|
||||
title: Message
|
||||
type: string
|
||||
station_id:
|
||||
title: Station Id
|
||||
type: integer
|
||||
success:
|
||||
title: Success
|
||||
type: boolean
|
||||
required:
|
||||
- success
|
||||
- station_id
|
||||
- material_id
|
||||
- material_number
|
||||
- message
|
||||
title: StartHeatingResult
|
||||
type: object
|
||||
required:
|
||||
- goal
|
||||
title: start_heating参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
module: unilabos.devices.virtual.workbench:VirtualWorkbench
|
||||
status_types:
|
||||
active_tasks_count: int
|
||||
arm_current_task: str
|
||||
arm_state: str
|
||||
heating_station_1_material: str
|
||||
heating_station_1_progress: float
|
||||
heating_station_1_state: str
|
||||
heating_station_2_material: str
|
||||
heating_station_2_progress: float
|
||||
heating_station_2_state: str
|
||||
heating_station_3_material: str
|
||||
heating_station_3_progress: float
|
||||
heating_station_3_state: str
|
||||
message: str
|
||||
status: str
|
||||
type: python
|
||||
config_info: []
|
||||
description: Virtual Workbench with 1 robotic arm and 3 heating stations for concurrent
|
||||
material processing
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema:
|
||||
config:
|
||||
properties:
|
||||
config:
|
||||
type: string
|
||||
device_id:
|
||||
type: string
|
||||
required: []
|
||||
type: object
|
||||
data:
|
||||
properties:
|
||||
active_tasks_count:
|
||||
type: integer
|
||||
arm_current_task:
|
||||
type: string
|
||||
arm_state:
|
||||
type: string
|
||||
heating_station_1_material:
|
||||
type: string
|
||||
heating_station_1_progress:
|
||||
type: number
|
||||
heating_station_1_state:
|
||||
type: string
|
||||
heating_station_2_material:
|
||||
type: string
|
||||
heating_station_2_progress:
|
||||
type: number
|
||||
heating_station_2_state:
|
||||
type: string
|
||||
heating_station_3_material:
|
||||
type: string
|
||||
heating_station_3_progress:
|
||||
type: number
|
||||
heating_station_3_state:
|
||||
type: string
|
||||
message:
|
||||
type: string
|
||||
status:
|
||||
type: string
|
||||
required:
|
||||
- status
|
||||
- arm_state
|
||||
- arm_current_task
|
||||
- heating_station_1_state
|
||||
- heating_station_1_material
|
||||
- heating_station_1_progress
|
||||
- heating_station_2_state
|
||||
- heating_station_2_material
|
||||
- heating_station_2_progress
|
||||
- heating_station_3_state
|
||||
- heating_station_3_material
|
||||
- heating_station_3_progress
|
||||
- active_tasks_count
|
||||
- message
|
||||
type: object
|
||||
version: 1.0.0
|
||||
|
||||
@@ -597,6 +597,8 @@ def resource_plr_to_ulab(resource_plr: "ResourcePLR", parent_name: str = None, w
|
||||
"tube": "tube",
|
||||
"bottle_carrier": "bottle_carrier",
|
||||
"plate_adapter": "plate_adapter",
|
||||
"electrode_sheet": "electrode_sheet",
|
||||
"material_hole": "material_hole",
|
||||
}
|
||||
if source in replace_info:
|
||||
return replace_info[source]
|
||||
|
||||
@@ -1320,19 +1320,32 @@ class BaseROS2DeviceNode(Node, Generic[T]):
|
||||
resource_inputs = action_kwargs[k] if is_sequence else [action_kwargs[k]]
|
||||
|
||||
# 批量查询资源
|
||||
queried_resources = []
|
||||
for resource_data in resource_inputs:
|
||||
queried_resources: list = [None] * len(resource_inputs)
|
||||
uuid_indices: list[tuple[int, str, dict]] = [] # (index, uuid, resource_data)
|
||||
|
||||
# 第一遍:处理没有uuid的资源,收集有uuid的资源信息
|
||||
for idx, resource_data in enumerate(resource_inputs):
|
||||
unilabos_uuid = resource_data.get("data", {}).get("unilabos_uuid")
|
||||
if unilabos_uuid is None:
|
||||
plr_resource = await self.get_resource_with_dir(
|
||||
resource_id=resource_data["id"], with_children=True
|
||||
)
|
||||
if "sample_id" in resource_data:
|
||||
plr_resource.unilabos_extra["sample_uuid"] = resource_data["sample_id"]
|
||||
queried_resources[idx] = plr_resource
|
||||
else:
|
||||
resource_tree = await self.get_resource([unilabos_uuid])
|
||||
plr_resource = resource_tree.to_plr_resources()[0]
|
||||
if "sample_id" in resource_data:
|
||||
plr_resource.unilabos_extra["sample_uuid"] = resource_data["sample_id"]
|
||||
queried_resources.append(plr_resource)
|
||||
uuid_indices.append((idx, unilabos_uuid, resource_data))
|
||||
|
||||
# 第二遍:批量查询有uuid的资源
|
||||
if uuid_indices:
|
||||
uuids = [item[1] for item in uuid_indices]
|
||||
resource_tree = await self.get_resource(uuids)
|
||||
plr_resources = resource_tree.to_plr_resources()
|
||||
for i, (idx, _, resource_data) in enumerate(uuid_indices):
|
||||
plr_resource = plr_resources[i]
|
||||
if "sample_id" in resource_data:
|
||||
plr_resource.unilabos_extra["sample_uuid"] = resource_data["sample_id"]
|
||||
queried_resources[idx] = plr_resource
|
||||
|
||||
self.lab_logger().debug(f"资源查询结果: 共 {len(queried_resources)} 个资源")
|
||||
|
||||
|
||||
@@ -795,6 +795,7 @@ class HostNode(BaseROS2DeviceNode):
|
||||
goal_msg = convert_to_ros_msg(action_client._action_type.Goal(), action_kwargs)
|
||||
|
||||
self.lab_logger().info(f"[Host Node] Sending goal for {action_id}: {str(goal_msg)[:1000]}")
|
||||
self.lab_logger().trace(f"[Host Node] Sending goal for {action_id}: {action_kwargs}")
|
||||
self.lab_logger().trace(f"[Host Node] Sending goal for {action_id}: {goal_msg}")
|
||||
action_client.wait_for_server()
|
||||
goal_uuid_obj = UUID(uuid=list(u.bytes))
|
||||
|
||||
@@ -24,6 +24,7 @@ class EnvironmentChecker:
|
||||
"msgcenterpy": "msgcenterpy",
|
||||
"opentrons_shared_data": "opentrons_shared_data",
|
||||
"typing_extensions": "typing_extensions",
|
||||
"crcmod": "crcmod-plus",
|
||||
}
|
||||
|
||||
# 特殊安装包(需要特殊处理的包)
|
||||
|
||||
18
unilabos/utils/requirements.txt
Normal file
18
unilabos/utils/requirements.txt
Normal file
@@ -0,0 +1,18 @@
|
||||
networkx
|
||||
typing_extensions
|
||||
websockets
|
||||
msgcenterpy>=0.1.5
|
||||
opentrons_shared_data
|
||||
pint
|
||||
fastapi
|
||||
jinja2
|
||||
requests
|
||||
uvicorn
|
||||
pyautogui
|
||||
opcua
|
||||
pyserial
|
||||
pandas
|
||||
crcmod-plus
|
||||
pymodbus
|
||||
matplotlib
|
||||
pylibftdi
|
||||
Reference in New Issue
Block a user