mirror of
https://github.com/dptech-corp/Uni-Lab-OS.git
synced 2026-02-13 11:15:12 +00:00
registry update & workflow update
This commit is contained in:
@@ -38,9 +38,9 @@ def register_devices_and_resources(lab_registry, gather_only=False) -> Optional[
|
|||||||
response = http_client.resource_registry({"resources": list(devices_to_register.values())})
|
response = http_client.resource_registry({"resources": list(devices_to_register.values())})
|
||||||
cost_time = time.time() - start_time
|
cost_time = time.time() - start_time
|
||||||
if response.status_code in [200, 201]:
|
if response.status_code in [200, 201]:
|
||||||
logger.info(f"[UniLab Register] 成功注册 {len(devices_to_register)} 个设备 {cost_time}ms")
|
logger.info(f"[UniLab Register] 成功注册 {len(devices_to_register)} 个设备 {cost_time}s")
|
||||||
else:
|
else:
|
||||||
logger.error(f"[UniLab Register] 设备注册失败: {response.status_code}, {response.text} {cost_time}ms")
|
logger.error(f"[UniLab Register] 设备注册失败: {response.status_code}, {response.text} {cost_time}s")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"[UniLab Register] 设备注册异常: {e}")
|
logger.error(f"[UniLab Register] 设备注册异常: {e}")
|
||||||
|
|
||||||
@@ -51,9 +51,9 @@ def register_devices_and_resources(lab_registry, gather_only=False) -> Optional[
|
|||||||
response = http_client.resource_registry({"resources": list(resources_to_register.values())})
|
response = http_client.resource_registry({"resources": list(resources_to_register.values())})
|
||||||
cost_time = time.time() - start_time
|
cost_time = time.time() - start_time
|
||||||
if response.status_code in [200, 201]:
|
if response.status_code in [200, 201]:
|
||||||
logger.info(f"[UniLab Register] 成功注册 {len(resources_to_register)} 个资源 {cost_time}ms")
|
logger.info(f"[UniLab Register] 成功注册 {len(resources_to_register)} 个资源 {cost_time}s")
|
||||||
else:
|
else:
|
||||||
logger.error(f"[UniLab Register] 资源注册失败: {response.status_code}, {response.text} {cost_time}ms")
|
logger.error(f"[UniLab Register] 资源注册失败: {response.status_code}, {response.text} {cost_time}s")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"[UniLab Register] 资源注册异常: {e}")
|
logger.error(f"[UniLab Register] 资源注册异常: {e}")
|
||||||
|
|
||||||
|
|||||||
@@ -96,10 +96,13 @@ serial:
|
|||||||
type: string
|
type: string
|
||||||
port:
|
port:
|
||||||
type: string
|
type: string
|
||||||
|
registry_name:
|
||||||
|
type: string
|
||||||
resource_tracker:
|
resource_tracker:
|
||||||
type: object
|
type: object
|
||||||
required:
|
required:
|
||||||
- device_id
|
- device_id
|
||||||
|
- registry_name
|
||||||
- port
|
- port
|
||||||
type: object
|
type: object
|
||||||
data:
|
data:
|
||||||
|
|||||||
@@ -67,6 +67,9 @@ camera:
|
|||||||
period:
|
period:
|
||||||
default: 0.1
|
default: 0.1
|
||||||
type: number
|
type: number
|
||||||
|
registry_name:
|
||||||
|
default: ''
|
||||||
|
type: string
|
||||||
resource_tracker:
|
resource_tracker:
|
||||||
type: object
|
type: object
|
||||||
required: []
|
required: []
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import sys
|
|||||||
import inspect
|
import inspect
|
||||||
import importlib
|
import importlib
|
||||||
import threading
|
import threading
|
||||||
|
import traceback
|
||||||
from concurrent.futures import ThreadPoolExecutor, as_completed
|
from concurrent.futures import ThreadPoolExecutor, as_completed
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any, Dict, List, Union, Tuple
|
from typing import Any, Dict, List, Union, Tuple
|
||||||
@@ -944,6 +945,7 @@ class Registry:
|
|||||||
if is_valid:
|
if is_valid:
|
||||||
results.append((file, data, device_ids))
|
results.append((file, data, device_ids))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
traceback.print_exc()
|
||||||
logger.warning(f"[UniLab Registry] 处理设备文件异常: {file}, 错误: {e}")
|
logger.warning(f"[UniLab Registry] 处理设备文件异常: {file}, 错误: {e}")
|
||||||
|
|
||||||
# 线程安全地更新注册表
|
# 线程安全地更新注册表
|
||||||
|
|||||||
@@ -362,14 +362,16 @@ def build_protocol_graph(
|
|||||||
protocol_steps: List[Dict[str, Any]],
|
protocol_steps: List[Dict[str, Any]],
|
||||||
workstation_name: str,
|
workstation_name: str,
|
||||||
action_resource_mapping: Optional[Dict[str, str]] = None,
|
action_resource_mapping: Optional[Dict[str, str]] = None,
|
||||||
|
labware_defs: Optional[List[Dict[str, Any]]] = None,
|
||||||
) -> WorkflowGraph:
|
) -> WorkflowGraph:
|
||||||
"""统一的协议图构建函数,根据设备类型自动选择构建逻辑
|
"""统一的协议图构建函数,根据设备类型自动选择构建逻辑
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
labware_info: labware 信息字典,格式为 {name: {slot, well, labware, ...}, ...}
|
labware_info: reagent 信息字典,格式为 {name: {slot, well}, ...},用于 set_liquid 和 well 查找
|
||||||
protocol_steps: 协议步骤列表
|
protocol_steps: 协议步骤列表
|
||||||
workstation_name: 工作站名称
|
workstation_name: 工作站名称
|
||||||
action_resource_mapping: action 到 resource_name 的映射字典,可选
|
action_resource_mapping: action 到 resource_name 的映射字典,可选
|
||||||
|
labware_defs: labware 定义列表,格式为 [{"name": "...", "slot": "1", "type": "lab_xxx"}, ...]
|
||||||
"""
|
"""
|
||||||
G = WorkflowGraph()
|
G = WorkflowGraph()
|
||||||
resource_last_writer = {} # reagent_name -> "node_id:port"
|
resource_last_writer = {} # reagent_name -> "node_id:port"
|
||||||
@@ -377,18 +379,7 @@ def build_protocol_graph(
|
|||||||
|
|
||||||
protocol_steps = refactor_data(protocol_steps, action_resource_mapping)
|
protocol_steps = refactor_data(protocol_steps, action_resource_mapping)
|
||||||
|
|
||||||
# ==================== 第一步:按 slot 去重创建 create_resource 节点 ====================
|
# ==================== 第一步:按 slot 创建 create_resource 节点 ====================
|
||||||
# 收集所有唯一的 slot
|
|
||||||
slots_info = {} # slot -> {labware, res_id}
|
|
||||||
for labware_id, item in labware_info.items():
|
|
||||||
slot = str(item.get("slot", ""))
|
|
||||||
if slot and slot not in slots_info:
|
|
||||||
res_id = f"plate_slot_{slot}"
|
|
||||||
slots_info[slot] = {
|
|
||||||
"labware": item.get("labware", ""),
|
|
||||||
"res_id": res_id,
|
|
||||||
}
|
|
||||||
|
|
||||||
# 创建 Group 节点,包含所有 create_resource 节点
|
# 创建 Group 节点,包含所有 create_resource 节点
|
||||||
group_node_id = str(uuid.uuid4())
|
group_node_id = str(uuid.uuid4())
|
||||||
G.add_node(
|
G.add_node(
|
||||||
@@ -404,29 +395,35 @@ def build_protocol_graph(
|
|||||||
param=None,
|
param=None,
|
||||||
)
|
)
|
||||||
|
|
||||||
# 为每个唯一的 slot 创建 create_resource 节点
|
# 直接使用 JSON 中的 labware 定义,每个 slot 一条记录,type 即 class_name
|
||||||
res_index = 0
|
res_index = 0
|
||||||
for slot, info in slots_info.items():
|
for lw in (labware_defs or []):
|
||||||
node_id = str(uuid.uuid4())
|
slot = str(lw.get("slot", ""))
|
||||||
res_id = info["res_id"]
|
if not slot or slot in slot_to_create_resource:
|
||||||
|
continue # 跳过空 slot 或已处理的 slot
|
||||||
|
|
||||||
|
lw_name = lw.get("name", f"slot {slot}")
|
||||||
|
lw_type = lw.get("type", CREATE_RESOURCE_DEFAULTS["class_name"])
|
||||||
|
res_id = f"plate_slot_{slot}"
|
||||||
|
|
||||||
res_index += 1
|
res_index += 1
|
||||||
|
node_id = str(uuid.uuid4())
|
||||||
G.add_node(
|
G.add_node(
|
||||||
node_id,
|
node_id,
|
||||||
template_name="create_resource",
|
template_name="create_resource",
|
||||||
resource_name="host_node",
|
resource_name="host_node",
|
||||||
name=f"Plate {res_index}",
|
name=lw_name,
|
||||||
description=f"Create plate on slot {slot}",
|
description=f"Create {lw_name}",
|
||||||
lab_node_type="Labware",
|
lab_node_type="Labware",
|
||||||
footer="create_resource-host_node",
|
footer="create_resource-host_node",
|
||||||
device_name=DEVICE_NAME_HOST,
|
device_name=DEVICE_NAME_HOST,
|
||||||
type=NODE_TYPE_DEFAULT,
|
type=NODE_TYPE_DEFAULT,
|
||||||
parent_uuid=group_node_id, # 指向 Group 节点
|
parent_uuid=group_node_id,
|
||||||
minimized=True, # 折叠显示
|
minimized=True,
|
||||||
param={
|
param={
|
||||||
"res_id": res_id,
|
"res_id": res_id,
|
||||||
"device_id": CREATE_RESOURCE_DEFAULTS["device_id"],
|
"device_id": CREATE_RESOURCE_DEFAULTS["device_id"],
|
||||||
"class_name": CREATE_RESOURCE_DEFAULTS["class_name"],
|
"class_name": lw_type,
|
||||||
"parent": CREATE_RESOURCE_DEFAULTS["parent_template"].format(slot=slot),
|
"parent": CREATE_RESOURCE_DEFAULTS["parent_template"].format(slot=slot),
|
||||||
"bind_locations": {"x": 0.0, "y": 0.0, "z": 0.0},
|
"bind_locations": {"x": 0.0, "y": 0.0, "z": 0.0},
|
||||||
"slot_on_deck": slot,
|
"slot_on_deck": slot,
|
||||||
@@ -434,8 +431,6 @@ def build_protocol_graph(
|
|||||||
)
|
)
|
||||||
slot_to_create_resource[slot] = node_id
|
slot_to_create_resource[slot] = node_id
|
||||||
|
|
||||||
# create_resource 之间不需要 ready 连接
|
|
||||||
|
|
||||||
# ==================== 第二步:为每个 reagent 创建 set_liquid_from_plate 节点 ====================
|
# ==================== 第二步:为每个 reagent 创建 set_liquid_from_plate 节点 ====================
|
||||||
# 创建 Group 节点,包含所有 set_liquid_from_plate 节点
|
# 创建 Group 节点,包含所有 set_liquid_from_plate 节点
|
||||||
set_liquid_group_id = str(uuid.uuid4())
|
set_liquid_group_id = str(uuid.uuid4())
|
||||||
|
|||||||
@@ -1,16 +1,20 @@
|
|||||||
"""
|
"""
|
||||||
JSON 工作流转换模块
|
JSON 工作流转换模块
|
||||||
|
|
||||||
将 workflow/reagent 格式的 JSON 转换为统一工作流格式。
|
将 workflow/reagent/labware 格式的 JSON 转换为统一工作流格式。
|
||||||
|
|
||||||
输入格式:
|
输入格式:
|
||||||
{
|
{
|
||||||
|
"labware": [
|
||||||
|
{"name": "...", "slot": "1", "type": "lab_xxx"},
|
||||||
|
...
|
||||||
|
],
|
||||||
"workflow": [
|
"workflow": [
|
||||||
{"action": "...", "action_args": {...}},
|
{"action": "...", "action_args": {...}},
|
||||||
...
|
...
|
||||||
],
|
],
|
||||||
"reagent": {
|
"reagent": {
|
||||||
"reagent_name": {"slot": int, "well": [...], "labware": "..."},
|
"reagent_name": {"slot": int, "well": [...]},
|
||||||
...
|
...
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -245,18 +249,18 @@ def convert_from_json(
|
|||||||
if "workflow" not in json_data or "reagent" not in json_data:
|
if "workflow" not in json_data or "reagent" not in json_data:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
"不支持的 JSON 格式。请使用标准格式:\n"
|
"不支持的 JSON 格式。请使用标准格式:\n"
|
||||||
'{"workflow": [{"action": "...", "action_args": {...}}, ...], '
|
'{"labware": [...], "workflow": [...], "reagent": {...}}'
|
||||||
'"reagent": {"name": {"slot": int, "well": [...], "labware": "..."}, ...}}'
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# 提取数据
|
# 提取数据
|
||||||
workflow = json_data["workflow"]
|
workflow = json_data["workflow"]
|
||||||
reagent = json_data["reagent"]
|
reagent = json_data["reagent"]
|
||||||
|
labware_defs = json_data.get("labware", []) # 新的 labware 定义列表
|
||||||
|
|
||||||
# 规范化步骤数据
|
# 规范化步骤数据
|
||||||
protocol_steps = normalize_workflow_steps(workflow)
|
protocol_steps = normalize_workflow_steps(workflow)
|
||||||
|
|
||||||
# reagent 已经是字典格式,直接使用
|
# reagent 已经是字典格式,用于 set_liquid 和 well 数量查找
|
||||||
labware_info = reagent
|
labware_info = reagent
|
||||||
|
|
||||||
# 构建工作流图
|
# 构建工作流图
|
||||||
@@ -265,6 +269,7 @@ def convert_from_json(
|
|||||||
protocol_steps=protocol_steps,
|
protocol_steps=protocol_steps,
|
||||||
workstation_name=workstation_name,
|
workstation_name=workstation_name,
|
||||||
action_resource_mapping=ACTION_RESOURCE_MAPPING,
|
action_resource_mapping=ACTION_RESOURCE_MAPPING,
|
||||||
|
labware_defs=labware_defs,
|
||||||
)
|
)
|
||||||
|
|
||||||
# 校验句柄配置
|
# 校验句柄配置
|
||||||
|
|||||||
Reference in New Issue
Block a user