mirror of
https://github.com/dptech-corp/Uni-Lab-OS.git
synced 2026-02-06 15:05:13 +00:00
更新中析仪器,以及启动示例
This commit is contained in:
190
test/experiments/prcxi.json
Normal file
190
test/experiments/prcxi.json
Normal file
@@ -0,0 +1,190 @@
|
|||||||
|
{
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"id": "PLR_STATION_PRCXI",
|
||||||
|
"name": "PLR_LH_TEST",
|
||||||
|
"parent": null,
|
||||||
|
"type": "device",
|
||||||
|
"class": "liquid_handler.prcxi",
|
||||||
|
"position": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"deck": {
|
||||||
|
"_resource_child_name": "deck",
|
||||||
|
"_resource_type": "unilabos.devices.liquid_handling.prcxi.prcxi:PRCXI9300Deck"
|
||||||
|
},
|
||||||
|
"host": "127.0.0.1",
|
||||||
|
"port": 9999,
|
||||||
|
"timeout": 10.0,
|
||||||
|
"setup": false
|
||||||
|
},
|
||||||
|
"data": {},
|
||||||
|
"children": [
|
||||||
|
"deck"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "deck",
|
||||||
|
"name": "deck",
|
||||||
|
"sample_id": null,
|
||||||
|
"children": [
|
||||||
|
"rackT1",
|
||||||
|
"plateT2",
|
||||||
|
"plateT3",
|
||||||
|
"rackT4",
|
||||||
|
"plateT5"
|
||||||
|
],
|
||||||
|
"parent": "PLR_STATION_PRCXI",
|
||||||
|
"type": "device",
|
||||||
|
"class": "",
|
||||||
|
"position": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"type": "PRCXI9300Deck"
|
||||||
|
},
|
||||||
|
"data": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "rackT1",
|
||||||
|
"name": "rackT1",
|
||||||
|
"sample_id": null,
|
||||||
|
"children": [],
|
||||||
|
"parent": "deck",
|
||||||
|
"type": "device",
|
||||||
|
"class": "",
|
||||||
|
"position": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"type": "PRCXI9300Container",
|
||||||
|
"size_x": 120.98,
|
||||||
|
"size_y": 82.12,
|
||||||
|
"size_z": 50.3
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"Material": {
|
||||||
|
"uuid": "80652665f6a54402b2408d50b40398df",
|
||||||
|
"Code": "ZX-001-1000",
|
||||||
|
"Name": "1000μL Tip头",
|
||||||
|
"SummaryName": "1000μL Tip头",
|
||||||
|
"PipetteHeight": 100,
|
||||||
|
"materialEnum": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "plateT2",
|
||||||
|
"name": "plateT2",
|
||||||
|
"sample_id": null,
|
||||||
|
"children": [],
|
||||||
|
"parent": "deck",
|
||||||
|
"type": "device",
|
||||||
|
"class": "",
|
||||||
|
"position": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"type": "PRCXI9300Container",
|
||||||
|
"size_x": 120.98,
|
||||||
|
"size_y": 82.12,
|
||||||
|
"size_z": 50.3
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"Material": {
|
||||||
|
"uuid": "57b1e4711e9e4a32b529f3132fc5931f"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "plateT3",
|
||||||
|
"name": "plateT3",
|
||||||
|
"sample_id": null,
|
||||||
|
"children": [],
|
||||||
|
"parent": "deck",
|
||||||
|
"type": "device",
|
||||||
|
"class": "",
|
||||||
|
"position": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"type": "PRCXI9300Container",
|
||||||
|
"size_x": 120.98,
|
||||||
|
"size_y": 82.12,
|
||||||
|
"size_z": 50.3
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"Material": {
|
||||||
|
"uuid": "57b1e4711e9e4a32b529f3132fc5931f"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "rackT4",
|
||||||
|
"name": "rackT4",
|
||||||
|
"sample_id": null,
|
||||||
|
"children": [],
|
||||||
|
"parent": "deck",
|
||||||
|
"type": "device",
|
||||||
|
"class": "",
|
||||||
|
"position": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"type": "PRCXI9300Container",
|
||||||
|
"size_x": 120.98,
|
||||||
|
"size_y": 82.12,
|
||||||
|
"size_z": 50.3
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"Material": {
|
||||||
|
"uuid": "80652665f6a54402b2408d50b40398df",
|
||||||
|
"Code": "ZX-001-1000",
|
||||||
|
"Name": "1000μL Tip头",
|
||||||
|
"SummaryName": "1000μL Tip头",
|
||||||
|
"PipetteHeight": 100,
|
||||||
|
"materialEnum": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "plateT5",
|
||||||
|
"name": "plateT5",
|
||||||
|
"sample_id": null,
|
||||||
|
"children": [],
|
||||||
|
"parent": "deck",
|
||||||
|
"type": "device",
|
||||||
|
"class": "",
|
||||||
|
"position": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"type": "PRCXI9300Container",
|
||||||
|
"size_x": 120.98,
|
||||||
|
"size_y": 82.12,
|
||||||
|
"size_z": 50.3
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"Material": {
|
||||||
|
"uuid": "57b1e4711e9e4a32b529f3132fc5931f"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"links": []
|
||||||
|
}
|
||||||
@@ -183,7 +183,7 @@ class LiquidHandlerAbstract(LiquidHandler):
|
|||||||
spread: Literal["wide", "tight", "custom"] = "wide",
|
spread: Literal["wide", "tight", "custom"] = "wide",
|
||||||
is_96_well: bool = False,
|
is_96_well: bool = False,
|
||||||
mix_stage: Optional[Literal["none", "before", "after", "both"]] = "none",
|
mix_stage: Optional[Literal["none", "before", "after", "both"]] = "none",
|
||||||
mix_times: Optional[List(int)] = None,
|
mix_times: Optional[List[int]] = None,
|
||||||
mix_vol: Optional[int] = None,
|
mix_vol: Optional[int] = None,
|
||||||
mix_rate: Optional[int] = None,
|
mix_rate: Optional[int] = None,
|
||||||
mix_liquid_height: Optional[float] = None,
|
mix_liquid_height: Optional[float] = None,
|
||||||
|
|||||||
@@ -1,17 +1,456 @@
|
|||||||
import socket, json, contextlib
|
import collections
|
||||||
from typing import Any, List, Dict, Optional
|
import contextlib
|
||||||
|
import json
|
||||||
|
import socket
|
||||||
|
import time
|
||||||
|
from typing import Any, List, Dict, Optional, TypedDict, Union, Sequence, Iterator, Literal
|
||||||
|
|
||||||
|
from pylabrobot.liquid_handling import (
|
||||||
|
LiquidHandlerBackend,
|
||||||
|
Pickup,
|
||||||
|
SingleChannelAspiration,
|
||||||
|
Drop,
|
||||||
|
SingleChannelDispense,
|
||||||
|
PickupTipRack,
|
||||||
|
DropTipRack,
|
||||||
|
MultiHeadAspirationPlate,
|
||||||
|
)
|
||||||
|
from pylabrobot.liquid_handling.standard import (
|
||||||
|
MultiHeadAspirationContainer,
|
||||||
|
MultiHeadDispenseContainer,
|
||||||
|
MultiHeadDispensePlate,
|
||||||
|
ResourcePickup,
|
||||||
|
ResourceMove,
|
||||||
|
ResourceDrop,
|
||||||
|
)
|
||||||
|
from pylabrobot.resources import Tip, Deck, Plate, Well, TipRack, Resource, Container, Coordinate
|
||||||
|
|
||||||
|
from unilabos.devices.liquid_handling.liquid_handler_abstract import LiquidHandlerAbstract
|
||||||
|
|
||||||
|
|
||||||
class PRCXIError(RuntimeError):
|
class PRCXIError(RuntimeError):
|
||||||
"""Lilith 返回 Success=false 时抛出的业务异常"""
|
"""Lilith 返回 Success=false 时抛出的业务异常"""
|
||||||
|
|
||||||
|
|
||||||
class PRCXI9300:
|
class Material(TypedDict): # 和Plate同关系
|
||||||
|
uuid: str
|
||||||
|
Code: Optional[str]
|
||||||
|
Name: Optional[str]
|
||||||
|
SummaryName: Optional[str]
|
||||||
|
PipetteHeight: Optional[int]
|
||||||
|
materialEnum: Optional[int]
|
||||||
|
|
||||||
def __init__(self, host: str = "127.0.0.1", port: int = 9999,
|
|
||||||
timeout: float = 10.0) -> None:
|
class WorkTablets(TypedDict):
|
||||||
|
Number: int
|
||||||
|
Code: str
|
||||||
|
Material: Dict[str, Any]
|
||||||
|
|
||||||
|
|
||||||
|
class MatrixInfo(TypedDict):
|
||||||
|
MatrixId: str
|
||||||
|
MatrixName: str
|
||||||
|
MatrixCount: int
|
||||||
|
WorkTablets: list[WorkTablets]
|
||||||
|
|
||||||
|
|
||||||
|
class PRCXI9300Deck(Deck):
|
||||||
|
"""PRCXI 9300 的专用 Deck 类,继承自 Deck。
|
||||||
|
|
||||||
|
该类定义了 PRCXI 9300 的工作台布局和槽位信息。
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, name: str, size_x: float, size_y: float, size_z: float):
|
||||||
|
super().__init__(name, size_x, size_y, size_z)
|
||||||
|
self.slots = [None] * 6 # PRCXI 9300 有 6 个槽位
|
||||||
|
|
||||||
|
|
||||||
|
class PRCXI9300Container(Plate):
|
||||||
|
"""PRCXI 9300 的专用 Deck 类,继承自 Deck。
|
||||||
|
|
||||||
|
该类定义了 PRCXI 9300 的工作台布局和槽位信息。
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, name: str, size_x: float, size_y: float, size_z: float, category: str):
|
||||||
|
super().__init__(name, size_x, size_y, size_z, category=category, ordering=collections.OrderedDict())
|
||||||
|
self._unilabos_state = {}
|
||||||
|
|
||||||
|
def load_state(self, state: Dict[str, Any]) -> None:
|
||||||
|
"""从给定的状态加载工作台信息。"""
|
||||||
|
super().load_state(state)
|
||||||
|
self._unilabos_state = state
|
||||||
|
|
||||||
|
def serialize_state(self) -> Dict[str, Dict[str, Any]]:
|
||||||
|
data = super().serialize_state()
|
||||||
|
data.update(self._unilabos_state)
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
class PRCXI9300Handler(LiquidHandlerAbstract):
|
||||||
|
def __init__(self, deck: Deck, host: str, port: int, timeout: float, setup=True):
|
||||||
|
tablets_info = []
|
||||||
|
count = 0
|
||||||
|
for child in deck.children:
|
||||||
|
if "Material" in child._unilabos_state:
|
||||||
|
count += 1
|
||||||
|
tablets_info.append(
|
||||||
|
WorkTablets(Number=count, Code=f"T{count}", Material=child._unilabos_state["Material"])
|
||||||
|
)
|
||||||
|
self._unilabos_backend = PRCXI9300Backend(tablets_info, host, port, timeout, setup)
|
||||||
|
super().__init__(backend=self._unilabos_backend, deck=deck)
|
||||||
|
|
||||||
|
async def create_protocol(
|
||||||
|
self,
|
||||||
|
protocol_name: str,
|
||||||
|
protocol_description: str,
|
||||||
|
protocol_version: str,
|
||||||
|
protocol_author: str,
|
||||||
|
protocol_date: str,
|
||||||
|
protocol_type: str,
|
||||||
|
none_keys: List[str] = [],
|
||||||
|
):
|
||||||
|
self._unilabos_backend.create_protocol(protocol_name)
|
||||||
|
|
||||||
|
async def run_protocol(self):
|
||||||
|
self._unilabos_backend.run_protocol()
|
||||||
|
|
||||||
|
async def remove_liquid(
|
||||||
|
self,
|
||||||
|
vols: List[float],
|
||||||
|
sources: Sequence[Container],
|
||||||
|
waste_liquid: Optional[Container] = None,
|
||||||
|
*,
|
||||||
|
use_channels: Optional[List[int]] = None,
|
||||||
|
flow_rates: Optional[List[Optional[float]]] = None,
|
||||||
|
offsets: Optional[List[Coordinate]] = None,
|
||||||
|
liquid_height: Optional[List[Optional[float]]] = None,
|
||||||
|
blow_out_air_volume: Optional[List[Optional[float]]] = None,
|
||||||
|
spread: Optional[Literal["wide", "tight", "custom"]] = "wide",
|
||||||
|
delays: Optional[List[int]] = None,
|
||||||
|
is_96_well: Optional[bool] = False,
|
||||||
|
top: Optional[List[float]] = None,
|
||||||
|
none_keys: List[str] = [],
|
||||||
|
):
|
||||||
|
return await super().remove_liquid(
|
||||||
|
vols,
|
||||||
|
sources,
|
||||||
|
waste_liquid,
|
||||||
|
use_channels=use_channels,
|
||||||
|
flow_rates=flow_rates,
|
||||||
|
offsets=offsets,
|
||||||
|
liquid_height=liquid_height,
|
||||||
|
blow_out_air_volume=blow_out_air_volume,
|
||||||
|
spread=spread,
|
||||||
|
delays=delays,
|
||||||
|
is_96_well=is_96_well,
|
||||||
|
top=top,
|
||||||
|
none_keys=none_keys,
|
||||||
|
)
|
||||||
|
|
||||||
|
async def add_liquid(
|
||||||
|
self,
|
||||||
|
asp_vols: Union[List[float], float],
|
||||||
|
dis_vols: Union[List[float], float],
|
||||||
|
reagent_sources: Sequence[Container],
|
||||||
|
targets: Sequence[Container],
|
||||||
|
*,
|
||||||
|
use_channels: Optional[List[int]] = None,
|
||||||
|
flow_rates: Optional[List[Optional[float]]] = None,
|
||||||
|
offsets: Optional[List[Coordinate]] = None,
|
||||||
|
liquid_height: Optional[List[Optional[float]]] = None,
|
||||||
|
blow_out_air_volume: Optional[List[Optional[float]]] = None,
|
||||||
|
spread: Optional[Literal["wide", "tight", "custom"]] = "wide",
|
||||||
|
is_96_well: bool = False,
|
||||||
|
delays: Optional[List[int]] = None,
|
||||||
|
mix_time: Optional[int] = None,
|
||||||
|
mix_vol: Optional[int] = None,
|
||||||
|
mix_rate: Optional[int] = None,
|
||||||
|
mix_liquid_height: Optional[float] = None,
|
||||||
|
none_keys: List[str] = [],
|
||||||
|
):
|
||||||
|
return await super().add_liquid(
|
||||||
|
asp_vols,
|
||||||
|
dis_vols,
|
||||||
|
reagent_sources,
|
||||||
|
targets,
|
||||||
|
use_channels=use_channels,
|
||||||
|
flow_rates=flow_rates,
|
||||||
|
offsets=offsets,
|
||||||
|
liquid_height=liquid_height,
|
||||||
|
blow_out_air_volume=blow_out_air_volume,
|
||||||
|
spread=spread,
|
||||||
|
is_96_well=is_96_well,
|
||||||
|
delays=delays,
|
||||||
|
mix_time=mix_time,
|
||||||
|
mix_vol=mix_vol,
|
||||||
|
mix_rate=mix_rate,
|
||||||
|
mix_liquid_height=mix_liquid_height,
|
||||||
|
none_keys=none_keys,
|
||||||
|
)
|
||||||
|
|
||||||
|
async def transfer_liquid(
|
||||||
|
self,
|
||||||
|
sources: Sequence[Container],
|
||||||
|
targets: Sequence[Container],
|
||||||
|
tip_racks: Sequence[TipRack],
|
||||||
|
*,
|
||||||
|
use_channels: Optional[List[int]] = None,
|
||||||
|
asp_vols: Union[List[float], float],
|
||||||
|
dis_vols: Union[List[float], float],
|
||||||
|
asp_flow_rates: Optional[List[Optional[float]]] = None,
|
||||||
|
dis_flow_rates: Optional[List[Optional[float]]] = None,
|
||||||
|
offsets: Optional[List[Coordinate]] = None,
|
||||||
|
touch_tip: bool = False,
|
||||||
|
liquid_height: Optional[List[Optional[float]]] = None,
|
||||||
|
blow_out_air_volume: Optional[List[Optional[float]]] = None,
|
||||||
|
spread: Literal["wide", "tight", "custom"] = "wide",
|
||||||
|
is_96_well: bool = False,
|
||||||
|
mix_stage: Optional[Literal["none", "before", "after", "both"]] = "none",
|
||||||
|
mix_times: Optional[List[int]] = None,
|
||||||
|
mix_vol: Optional[int] = None,
|
||||||
|
mix_rate: Optional[int] = None,
|
||||||
|
mix_liquid_height: Optional[float] = None,
|
||||||
|
delays: Optional[List[int]] = None,
|
||||||
|
none_keys: List[str] = [],
|
||||||
|
):
|
||||||
|
return await super().transfer_liquid(
|
||||||
|
sources,
|
||||||
|
targets,
|
||||||
|
tip_racks,
|
||||||
|
use_channels=use_channels,
|
||||||
|
asp_vols=asp_vols,
|
||||||
|
dis_vols=dis_vols,
|
||||||
|
asp_flow_rates=asp_flow_rates,
|
||||||
|
dis_flow_rates=dis_flow_rates,
|
||||||
|
offsets=offsets,
|
||||||
|
touch_tip=touch_tip,
|
||||||
|
liquid_height=liquid_height,
|
||||||
|
blow_out_air_volume=blow_out_air_volume,
|
||||||
|
spread=spread,
|
||||||
|
is_96_well=is_96_well,
|
||||||
|
mix_stage=mix_stage,
|
||||||
|
mix_times=mix_times,
|
||||||
|
mix_vol=mix_vol,
|
||||||
|
mix_rate=mix_rate,
|
||||||
|
mix_liquid_height=mix_liquid_height,
|
||||||
|
delays=delays,
|
||||||
|
none_keys=none_keys,
|
||||||
|
)
|
||||||
|
|
||||||
|
async def custom_delay(self, seconds=0, msg=None):
|
||||||
|
return await super().custom_delay(seconds, msg)
|
||||||
|
|
||||||
|
async def touch_tip(self, targets: Sequence[Container]):
|
||||||
|
return await super().touch_tip(targets)
|
||||||
|
|
||||||
|
async def mix(
|
||||||
|
self,
|
||||||
|
targets: Sequence[Container],
|
||||||
|
mix_time: int = None,
|
||||||
|
mix_vol: Optional[int] = None,
|
||||||
|
height_to_bottom: Optional[float] = None,
|
||||||
|
offsets: Optional[Coordinate] = None,
|
||||||
|
mix_rate: Optional[float] = None,
|
||||||
|
none_keys: List[str] = [],
|
||||||
|
):
|
||||||
|
return await super().mix(targets, mix_time, mix_vol, height_to_bottom, offsets, mix_rate, none_keys)
|
||||||
|
|
||||||
|
def iter_tips(self, tip_racks: Sequence[TipRack]) -> Iterator[Resource]:
|
||||||
|
return super().iter_tips(tip_racks)
|
||||||
|
|
||||||
|
def set_tiprack(self, tip_racks: Sequence[TipRack]):
|
||||||
|
super().set_tiprack(tip_racks)
|
||||||
|
|
||||||
|
async def move_to(self, well: Well, dis_to_top: float = 0, channel: int = 0):
|
||||||
|
return await super().move_to(well, dis_to_top, channel)
|
||||||
|
|
||||||
|
|
||||||
|
class PRCXI9300Backend(LiquidHandlerBackend):
|
||||||
|
"""PRCXI 9300 的后端实现,继承自 LiquidHandlerBackend。
|
||||||
|
|
||||||
|
该类提供了与 PRCXI 9300 设备进行通信的基本方法,包括方案管理、自动化控制、运行状态查询等。
|
||||||
|
"""
|
||||||
|
|
||||||
|
_num_channels = 8 # 默认通道数为 8
|
||||||
|
matrix_info: MatrixInfo
|
||||||
|
protocol_name: str
|
||||||
|
steps_todo_list = []
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
tablets_info: list[WorkTablets],
|
||||||
|
host: str = "127.0.0.1",
|
||||||
|
port: int = 9999,
|
||||||
|
timeout: float = 10.0,
|
||||||
|
setup=True,
|
||||||
|
) -> None:
|
||||||
|
super().__init__()
|
||||||
|
self.tablets_info = tablets_info
|
||||||
|
self.api_client = PRCXI9300Api(host, port, timeout)
|
||||||
self.host, self.port, self.timeout = host, port, timeout
|
self.host, self.port, self.timeout = host, port, timeout
|
||||||
|
self._num_channels = 8
|
||||||
|
self._execute_setup = setup
|
||||||
|
|
||||||
|
def create_protocol(self, protocol_name):
|
||||||
|
self.protocol_name = protocol_name
|
||||||
|
self.steps_todo_list = []
|
||||||
|
|
||||||
|
def run_protocol(self):
|
||||||
|
run_time = time.time()
|
||||||
|
self.matrix_info = MatrixInfo(
|
||||||
|
MatrixId=f"matrix_{run_time}",
|
||||||
|
MatrixName=f"protocol_{run_time}",
|
||||||
|
MatrixCount=len(self.tablets_info),
|
||||||
|
WorkTablets=self.tablets_info,
|
||||||
|
)
|
||||||
|
self.api_client.add_WorkTablet_Matrix(self.matrix_info)
|
||||||
|
solution_id = self.api_client.add_solution(
|
||||||
|
f"protocol_{run_time}", self.matrix_info["MatrixId"], self.steps_todo_list
|
||||||
|
)
|
||||||
|
self.api_client.load_solution(solution_id)
|
||||||
|
self.api_client.start()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def check_channels(cls, use_channels: List[int]) -> List[int]:
|
||||||
|
"""检查通道是否符合要求,PRCXI9300Backend 只支持所有 8 个通道。"""
|
||||||
|
if use_channels != [0, 1, 2, 3, 4, 5, 6, 7]:
|
||||||
|
print("PRCXI9300Backend only supports all 8 channels, using default [0, 1, 2, 3, 4, 5, 6, 7].")
|
||||||
|
return [0, 1, 2, 3, 4, 5, 6, 7]
|
||||||
|
return use_channels
|
||||||
|
|
||||||
|
async def setup(self):
|
||||||
|
await super().setup()
|
||||||
|
try:
|
||||||
|
if self._execute_setup:
|
||||||
|
self.api_client.call("IAutomation", "Reset")
|
||||||
|
except ConnectionRefusedError as e:
|
||||||
|
raise RuntimeError(
|
||||||
|
f"Failed to connect to PRCXI9300 API at {self.host}:{self.port}. "
|
||||||
|
"Please ensure the PRCXI9300 service is running."
|
||||||
|
) from e
|
||||||
|
|
||||||
|
async def stop(self):
|
||||||
|
self.api_client.call("IAutomation", "Stop")
|
||||||
|
|
||||||
|
async def pick_up_tips(self, ops: List[Pickup], use_channels: List[int] = None):
|
||||||
|
"""Pick up tips from the specified resource."""
|
||||||
|
# 12列,要PickUp A-H 1
|
||||||
|
# PlateNo = 1-6 # 2行3列
|
||||||
|
PlateNo = 1 # 第一块板
|
||||||
|
hole_col = 2 # 第二列的8个孔
|
||||||
|
step = self.api_client.Load(
|
||||||
|
"Left",
|
||||||
|
dosage=0,
|
||||||
|
plate_no=PlateNo,
|
||||||
|
is_whole_plate=False,
|
||||||
|
hole_row=1,
|
||||||
|
hole_col=hole_col,
|
||||||
|
blending_times=0,
|
||||||
|
balance_height=0,
|
||||||
|
plate_or_hole=f"H{hole_col}-8,T{PlateNo}",
|
||||||
|
hole_numbers="1,2,3,4,5,6,7,8",
|
||||||
|
)
|
||||||
|
self.steps_todo_list.append(step)
|
||||||
|
print("PRCXI9300Backend pick_up_tips logged.")
|
||||||
|
|
||||||
|
async def drop_tips(self, ops: List[Drop], use_channels: List[int] = None):
|
||||||
|
PlateNo = 1
|
||||||
|
hole_col = 2
|
||||||
|
step = self.api_client.UnLoad(
|
||||||
|
"Left",
|
||||||
|
dosage=0,
|
||||||
|
plate_no=PlateNo,
|
||||||
|
is_whole_plate=False,
|
||||||
|
hole_row=1,
|
||||||
|
hole_col=hole_col,
|
||||||
|
blending_times=0,
|
||||||
|
balance_height=0,
|
||||||
|
plate_or_hole=f"H{hole_col}-8,T{PlateNo}",
|
||||||
|
hole_numbers="1,2,3,4,5,6,7,8",
|
||||||
|
)
|
||||||
|
allow_drop = False
|
||||||
|
for s in self.steps_todo_list:
|
||||||
|
if s.get("Function") == "Load":
|
||||||
|
self.steps_todo_list.append(step)
|
||||||
|
allow_drop = True
|
||||||
|
break
|
||||||
|
if not allow_drop:
|
||||||
|
raise ValueError("No matching Load step found for drop_tips.")
|
||||||
|
print("PRCXI9300Backend drop_tips logged.")
|
||||||
|
|
||||||
|
async def aspirate(self, ops: List[SingleChannelAspiration], use_channels: List[int] = None):
|
||||||
|
volumes = [op.volume for op in ops]
|
||||||
|
print(f"PRCXI9300Backend aspirate volumes: {volumes[0]} -> {int(volumes[0])} μL")
|
||||||
|
PlateNo = 1
|
||||||
|
hole_col = 2
|
||||||
|
step = self.api_client.Imbibing(
|
||||||
|
"Left",
|
||||||
|
dosage=int(volumes[0]),
|
||||||
|
plate_no=PlateNo,
|
||||||
|
is_whole_plate=False,
|
||||||
|
hole_row=1,
|
||||||
|
hole_col=hole_col,
|
||||||
|
blending_times=0,
|
||||||
|
balance_height=0,
|
||||||
|
plate_or_hole=f"H{hole_col}-8,T{PlateNo}",
|
||||||
|
hole_numbers="1,2,3,4,5,6,7,8",
|
||||||
|
)
|
||||||
|
self.steps_todo_list.append(step)
|
||||||
|
|
||||||
|
async def dispense(self, ops: List[SingleChannelDispense], use_channels: List[int] = None):
|
||||||
|
volumes = [op.volume for op in ops]
|
||||||
|
print(f"PRCXI9300Backend dispense volumes: {volumes[0]} -> {int(volumes[0])} μL")
|
||||||
|
PlateNo = 1
|
||||||
|
hole_col = 2
|
||||||
|
step = self.api_client.Tapping(
|
||||||
|
"Left",
|
||||||
|
dosage=int(volumes[0]),
|
||||||
|
plate_no=PlateNo,
|
||||||
|
is_whole_plate=False,
|
||||||
|
hole_row=1,
|
||||||
|
hole_col=hole_col,
|
||||||
|
blending_times=0,
|
||||||
|
balance_height=0,
|
||||||
|
plate_or_hole=f"H{hole_col}-8,T{PlateNo}",
|
||||||
|
hole_numbers="1,2,3,4,5,6,7,8",
|
||||||
|
)
|
||||||
|
self.steps_todo_list.append(step)
|
||||||
|
|
||||||
|
async def pick_up_tips96(self, pickup: PickupTipRack):
|
||||||
|
raise NotImplementedError("The PRCXI backend does not support the 96 head.")
|
||||||
|
|
||||||
|
async def drop_tips96(self, drop: DropTipRack):
|
||||||
|
raise NotImplementedError("The PRCXI backend does not support the 96 head.")
|
||||||
|
|
||||||
|
async def aspirate96(self, aspiration: Union[MultiHeadAspirationPlate, MultiHeadAspirationContainer]):
|
||||||
|
raise NotImplementedError("The Opentrons backend does not support the 96 head.")
|
||||||
|
|
||||||
|
async def dispense96(self, dispense: Union[MultiHeadDispensePlate, MultiHeadDispenseContainer]):
|
||||||
|
raise NotImplementedError("The Opentrons backend does not support the 96 head.")
|
||||||
|
|
||||||
|
async def pick_up_resource(self, pickup: ResourcePickup):
|
||||||
|
raise NotImplementedError("The Opentrons backend does not support the robotic arm.")
|
||||||
|
|
||||||
|
async def move_picked_up_resource(self, move: ResourceMove):
|
||||||
|
raise NotImplementedError("The Opentrons backend does not support the robotic arm.")
|
||||||
|
|
||||||
|
async def drop_resource(self, drop: ResourceDrop):
|
||||||
|
raise NotImplementedError("The Opentrons backend does not support the robotic arm.")
|
||||||
|
|
||||||
|
def can_pick_up_tip(self, channel_idx: int, tip: Tip) -> bool:
|
||||||
|
return True # PRCXI9300Backend does not have tip compatibility issues
|
||||||
|
|
||||||
|
def serialize(self) -> dict:
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def num_channels(self) -> int:
|
||||||
|
return self._num_channels
|
||||||
|
|
||||||
|
|
||||||
|
class PRCXI9300Api:
|
||||||
|
def __init__(self, host: str = "127.0.0.1", port: int = 9999, timeout: float = 10.0) -> None:
|
||||||
|
self.host, self.port, self.timeout = host, port, timeout
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _len_prefix(n: int) -> bytes:
|
def _len_prefix(n: int) -> bytes:
|
||||||
@@ -30,17 +469,30 @@ class PRCXI9300:
|
|||||||
if not chunk:
|
if not chunk:
|
||||||
break
|
break
|
||||||
if first:
|
if first:
|
||||||
chunk, first = chunk[8:], False
|
chunk, first = chunk[8:], False
|
||||||
chunks.append(chunk)
|
chunks.append(chunk)
|
||||||
return b"".join(chunks).decode()
|
return b"".join(chunks).decode()
|
||||||
|
|
||||||
def _call(self, service: str, method: str,
|
# ---------------------------------------------------- 方案相关(ISolution)
|
||||||
params: Optional[list] = None) -> Any:
|
def list_solutions(self) -> List[Dict[str, Any]]:
|
||||||
|
"""GetSolutionList"""
|
||||||
|
return self.call("ISolution", "GetSolutionList")
|
||||||
|
|
||||||
|
def load_solution(self, solution_id: str) -> bool:
|
||||||
|
"""LoadSolution"""
|
||||||
|
return self.call("ISolution", "LoadSolution", [solution_id])
|
||||||
|
|
||||||
|
def add_solution(self, name: str, matrix_id: str, steps: List[Dict[str, Any]]) -> str:
|
||||||
|
"""AddSolution → 返回新方案 GUID"""
|
||||||
|
return self.call("ISolution", "AddSolution", [name, matrix_id, steps])
|
||||||
|
|
||||||
|
# ---------------------------------------------------- 自动化控制(IAutomation)
|
||||||
|
def start(self) -> bool:
|
||||||
|
return self.call("IAutomation", "Start")
|
||||||
|
|
||||||
|
def call(self, service: str, method: str, params: Optional[list] = None) -> Any:
|
||||||
payload = json.dumps(
|
payload = json.dumps(
|
||||||
{"ServiceName": service,
|
{"ServiceName": service, "MethodName": method, "Paramters": params or []}, separators=(",", ":")
|
||||||
"MethodName": method,
|
|
||||||
"Paramters": params or []},
|
|
||||||
separators=(",", ":")
|
|
||||||
)
|
)
|
||||||
resp = json.loads(self._raw_request(payload))
|
resp = json.loads(self._raw_request(payload))
|
||||||
if not resp.get("Success", False):
|
if not resp.get("Success", False):
|
||||||
@@ -51,87 +503,53 @@ class PRCXI9300:
|
|||||||
except (TypeError, json.JSONDecodeError):
|
except (TypeError, json.JSONDecodeError):
|
||||||
return data
|
return data
|
||||||
|
|
||||||
# ---------------------------------------------------- 方案相关(ISolution)
|
|
||||||
def list_solutions(self) -> List[Dict[str, Any]]:
|
|
||||||
"""GetSolutionList"""
|
|
||||||
return self._call("ISolution", "GetSolutionList")
|
|
||||||
|
|
||||||
def load_solution(self, solution_id: str) -> bool:
|
|
||||||
"""LoadSolution"""
|
|
||||||
return self._call("ISolution", "LoadSolution", [solution_id])
|
|
||||||
|
|
||||||
def add_solution(self, name: str, matrix_id: str,
|
|
||||||
steps: List[Dict[str, Any]]) -> str:
|
|
||||||
"""AddSolution → 返回新方案 GUID"""
|
|
||||||
return self._call("ISolution", "AddSolution",
|
|
||||||
[name, matrix_id, steps])
|
|
||||||
|
|
||||||
# ---------------------------------------------------- 自动化控制(IAutomation)
|
|
||||||
def start(self) -> bool:
|
|
||||||
return self._call("IAutomation", "Start")
|
|
||||||
|
|
||||||
def stop(self) -> bool:
|
|
||||||
"""Stop"""
|
|
||||||
return self._call("IAutomation", "Stop")
|
|
||||||
|
|
||||||
def reset(self) -> bool:
|
|
||||||
"""Reset"""
|
|
||||||
return self._call("IAutomation", "Reset")
|
|
||||||
|
|
||||||
def pause(self) -> bool:
|
def pause(self) -> bool:
|
||||||
"""Pause"""
|
"""Pause"""
|
||||||
return self._call("IAutomation", "Pause")
|
return self.call("IAutomation", "Pause")
|
||||||
|
|
||||||
def resume(self) -> bool:
|
def resume(self) -> bool:
|
||||||
"""Resume"""
|
"""Resume"""
|
||||||
return self._call("IAutomation", "Resume")
|
return self.call("IAutomation", "Resume")
|
||||||
|
|
||||||
def get_error_code(self) -> Optional[str]:
|
def get_error_code(self) -> Optional[str]:
|
||||||
"""GetErrorCode"""
|
"""GetErrorCode"""
|
||||||
return self._call("IAutomation", "GetErrorCode")
|
return self.call("IAutomation", "GetErrorCode")
|
||||||
|
|
||||||
def clear_error_code(self) -> bool:
|
def clear_error_code(self) -> bool:
|
||||||
"""RemoveErrorCodet"""
|
"""RemoveErrorCodet"""
|
||||||
return self._call("IAutomation", "RemoveErrorCodet")
|
return self.call("IAutomation", "RemoveErrorCodet")
|
||||||
|
|
||||||
# ---------------------------------------------------- 运行状态(IMachineState)
|
# ---------------------------------------------------- 运行状态(IMachineState)
|
||||||
def step_state_list(self) -> List[Dict[str, Any]]:
|
def step_state_list(self) -> List[Dict[str, Any]]:
|
||||||
"""GetStepStateList"""
|
"""GetStepStateList"""
|
||||||
return self._call("IMachineState", "GetStepStateList")
|
return self.call("IMachineState", "GetStepStateList")
|
||||||
|
|
||||||
def step_status(self, seq_num: int) -> Dict[str, Any]:
|
def step_status(self, seq_num: int) -> Dict[str, Any]:
|
||||||
"""GetStepStatus"""
|
"""GetStepStatus"""
|
||||||
return self._call("IMachineState", "GetStepStatus", [seq_num])
|
return self.call("IMachineState", "GetStepStatus", [seq_num])
|
||||||
|
|
||||||
def step_state(self, seq_num: int) -> Dict[str, Any]:
|
def step_state(self, seq_num: int) -> Dict[str, Any]:
|
||||||
"""GetStepState"""
|
"""GetStepState"""
|
||||||
return self._call("IMachineState", "GetStepState", [seq_num])
|
return self.call("IMachineState", "GetStepState", [seq_num])
|
||||||
|
|
||||||
def axis_location(self, axis_num: int = 1) -> Dict[str, Any]:
|
def axis_location(self, axis_num: int = 1) -> Dict[str, Any]:
|
||||||
"""GetLocation"""
|
"""GetLocation"""
|
||||||
return self._call("IMachineState", "GetLocation", [axis_num])
|
return self.call("IMachineState", "GetLocation", [axis_num])
|
||||||
|
|
||||||
# ---------------------------------------------------- 版位矩阵(IMatrix)
|
# ---------------------------------------------------- 版位矩阵(IMatrix)
|
||||||
def list_matrices(self) -> List[Dict[str, Any]]:
|
def list_matrices(self) -> List[Dict[str, Any]]:
|
||||||
"""GetWorkTabletMatrices"""
|
"""GetWorkTabletMatrices"""
|
||||||
return self._call("IMatrix", "GetWorkTabletMatrices")
|
return self.call("IMatrix", "GetWorkTabletMatrices")
|
||||||
|
|
||||||
def matrix_by_id(self, matrix_id: str) -> Dict[str, Any]:
|
def matrix_by_id(self, matrix_id: str) -> Dict[str, Any]:
|
||||||
"""GetWorkTabletMatrixById"""
|
"""GetWorkTabletMatrixById"""
|
||||||
return self._call("IMatrix", "GetWorkTabletMatrixById", [matrix_id])
|
return self.call("IMatrix", "GetWorkTabletMatrixById", [matrix_id])
|
||||||
|
|
||||||
def add_WorkTablet_Matrix(self,matrix):
|
|
||||||
return self._call("IMatrix", "AddWorkTabletMatrix", [matrix])
|
|
||||||
|
|
||||||
# ---------------------------------------------------- 一键运行
|
def add_WorkTablet_Matrix(self, matrix: MatrixInfo):
|
||||||
def run_solution(self, solution_id: str, channel_idx: int = 1) -> None:
|
return self.call("IMatrix", "AddWorkTabletMatrix", [matrix])
|
||||||
self.load_solution(solution_id)
|
|
||||||
self.start(channel_idx)
|
|
||||||
|
|
||||||
# ---------------------------------------------------- 单点动作
|
|
||||||
|
|
||||||
def Load(
|
def Load(
|
||||||
self,
|
self,
|
||||||
axis: str,
|
axis: str,
|
||||||
dosage: int,
|
dosage: int,
|
||||||
plate_no: int,
|
plate_no: int,
|
||||||
@@ -147,7 +565,7 @@ class PRCXI9300:
|
|||||||
assist_fun3: str = "",
|
assist_fun3: str = "",
|
||||||
assist_fun4: str = "",
|
assist_fun4: str = "",
|
||||||
assist_fun5: str = "",
|
assist_fun5: str = "",
|
||||||
liquid_method: str = "NormalDispense"
|
liquid_method: str = "NormalDispense",
|
||||||
) -> Dict[str, Any]:
|
) -> Dict[str, Any]:
|
||||||
return {
|
return {
|
||||||
"StepAxis": axis,
|
"StepAxis": axis,
|
||||||
@@ -166,11 +584,11 @@ class PRCXI9300:
|
|||||||
"AssistFun4": assist_fun4,
|
"AssistFun4": assist_fun4,
|
||||||
"AssistFun5": assist_fun5,
|
"AssistFun5": assist_fun5,
|
||||||
"HoleNumbers": hole_numbers,
|
"HoleNumbers": hole_numbers,
|
||||||
"LiquidDispensingMethod": liquid_method
|
"LiquidDispensingMethod": liquid_method,
|
||||||
}
|
}
|
||||||
|
|
||||||
def Imbibing(
|
def Imbibing(
|
||||||
self,
|
self,
|
||||||
axis: str,
|
axis: str,
|
||||||
dosage: int,
|
dosage: int,
|
||||||
plate_no: int,
|
plate_no: int,
|
||||||
@@ -186,7 +604,7 @@ class PRCXI9300:
|
|||||||
assist_fun3: str = "",
|
assist_fun3: str = "",
|
||||||
assist_fun4: str = "",
|
assist_fun4: str = "",
|
||||||
assist_fun5: str = "",
|
assist_fun5: str = "",
|
||||||
liquid_method: str = "NormalDispense"
|
liquid_method: str = "NormalDispense",
|
||||||
) -> Dict[str, Any]:
|
) -> Dict[str, Any]:
|
||||||
return {
|
return {
|
||||||
"StepAxis": axis,
|
"StepAxis": axis,
|
||||||
@@ -205,12 +623,11 @@ class PRCXI9300:
|
|||||||
"AssistFun4": assist_fun4,
|
"AssistFun4": assist_fun4,
|
||||||
"AssistFun5": assist_fun5,
|
"AssistFun5": assist_fun5,
|
||||||
"HoleNumbers": hole_numbers,
|
"HoleNumbers": hole_numbers,
|
||||||
"LiquidDispensingMethod": liquid_method
|
"LiquidDispensingMethod": liquid_method,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def Tapping(
|
def Tapping(
|
||||||
self,
|
self,
|
||||||
axis: str,
|
axis: str,
|
||||||
dosage: int,
|
dosage: int,
|
||||||
plate_no: int,
|
plate_no: int,
|
||||||
@@ -226,7 +643,7 @@ class PRCXI9300:
|
|||||||
assist_fun3: str = "",
|
assist_fun3: str = "",
|
||||||
assist_fun4: str = "",
|
assist_fun4: str = "",
|
||||||
assist_fun5: str = "",
|
assist_fun5: str = "",
|
||||||
liquid_method: str = "NormalDispense"
|
liquid_method: str = "NormalDispense",
|
||||||
) -> Dict[str, Any]:
|
) -> Dict[str, Any]:
|
||||||
return {
|
return {
|
||||||
"StepAxis": axis,
|
"StepAxis": axis,
|
||||||
@@ -245,12 +662,11 @@ class PRCXI9300:
|
|||||||
"AssistFun4": assist_fun4,
|
"AssistFun4": assist_fun4,
|
||||||
"AssistFun5": assist_fun5,
|
"AssistFun5": assist_fun5,
|
||||||
"HoleNumbers": hole_numbers,
|
"HoleNumbers": hole_numbers,
|
||||||
"LiquidDispensingMethod": liquid_method
|
"LiquidDispensingMethod": liquid_method,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def Blending(
|
def Blending(
|
||||||
self,
|
self,
|
||||||
axis: str,
|
axis: str,
|
||||||
dosage: int,
|
dosage: int,
|
||||||
plate_no: int,
|
plate_no: int,
|
||||||
@@ -266,7 +682,7 @@ class PRCXI9300:
|
|||||||
assist_fun3: str = "",
|
assist_fun3: str = "",
|
||||||
assist_fun4: str = "",
|
assist_fun4: str = "",
|
||||||
assist_fun5: str = "",
|
assist_fun5: str = "",
|
||||||
liquid_method: str = "NormalDispense"
|
liquid_method: str = "NormalDispense",
|
||||||
) -> Dict[str, Any]:
|
) -> Dict[str, Any]:
|
||||||
return {
|
return {
|
||||||
"StepAxis": axis,
|
"StepAxis": axis,
|
||||||
@@ -285,11 +701,11 @@ class PRCXI9300:
|
|||||||
"AssistFun4": assist_fun4,
|
"AssistFun4": assist_fun4,
|
||||||
"AssistFun5": assist_fun5,
|
"AssistFun5": assist_fun5,
|
||||||
"HoleNumbers": hole_numbers,
|
"HoleNumbers": hole_numbers,
|
||||||
"LiquidDispensingMethod": liquid_method
|
"LiquidDispensingMethod": liquid_method,
|
||||||
}
|
}
|
||||||
|
|
||||||
def UnLoad(
|
def UnLoad(
|
||||||
self,
|
self,
|
||||||
axis: str,
|
axis: str,
|
||||||
dosage: int,
|
dosage: int,
|
||||||
plate_no: int,
|
plate_no: int,
|
||||||
@@ -305,7 +721,7 @@ class PRCXI9300:
|
|||||||
assist_fun3: str = "",
|
assist_fun3: str = "",
|
||||||
assist_fun4: str = "",
|
assist_fun4: str = "",
|
||||||
assist_fun5: str = "",
|
assist_fun5: str = "",
|
||||||
liquid_method: str = "NormalDispense"
|
liquid_method: str = "NormalDispense",
|
||||||
) -> Dict[str, Any]:
|
) -> Dict[str, Any]:
|
||||||
return {
|
return {
|
||||||
"StepAxis": axis,
|
"StepAxis": axis,
|
||||||
@@ -324,5 +740,5 @@ class PRCXI9300:
|
|||||||
"AssistFun4": assist_fun4,
|
"AssistFun4": assist_fun4,
|
||||||
"AssistFun5": assist_fun5,
|
"AssistFun5": assist_fun5,
|
||||||
"HoleNumbers": hole_numbers,
|
"HoleNumbers": hole_numbers,
|
||||||
"LiquidDispensingMethod": liquid_method
|
"LiquidDispensingMethod": liquid_method,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6064,6 +6064,516 @@ liquid_handler.biomek:
|
|||||||
required:
|
required:
|
||||||
- success
|
- success
|
||||||
type: object
|
type: object
|
||||||
|
liquid_handler.prcxi:
|
||||||
|
class:
|
||||||
|
action_value_mappings:
|
||||||
|
auto-add_liquid:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
asp_vols: null
|
||||||
|
blow_out_air_volume: null
|
||||||
|
delays: null
|
||||||
|
dis_vols: null
|
||||||
|
flow_rates: null
|
||||||
|
is_96_well: false
|
||||||
|
liquid_height: null
|
||||||
|
mix_liquid_height: null
|
||||||
|
mix_rate: null
|
||||||
|
mix_time: null
|
||||||
|
mix_vol: null
|
||||||
|
none_keys: []
|
||||||
|
offsets: null
|
||||||
|
reagent_sources: null
|
||||||
|
spread: wide
|
||||||
|
targets: null
|
||||||
|
use_channels: null
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: add_liquid的参数schema
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
asp_vols:
|
||||||
|
type: string
|
||||||
|
blow_out_air_volume:
|
||||||
|
type: string
|
||||||
|
delays:
|
||||||
|
type: string
|
||||||
|
dis_vols:
|
||||||
|
type: string
|
||||||
|
flow_rates:
|
||||||
|
type: string
|
||||||
|
is_96_well:
|
||||||
|
default: false
|
||||||
|
type: boolean
|
||||||
|
liquid_height:
|
||||||
|
type: string
|
||||||
|
mix_liquid_height:
|
||||||
|
type: string
|
||||||
|
mix_rate:
|
||||||
|
type: string
|
||||||
|
mix_time:
|
||||||
|
type: string
|
||||||
|
mix_vol:
|
||||||
|
type: string
|
||||||
|
none_keys:
|
||||||
|
default: []
|
||||||
|
type: array
|
||||||
|
offsets:
|
||||||
|
type: string
|
||||||
|
reagent_sources:
|
||||||
|
type: string
|
||||||
|
spread:
|
||||||
|
default: wide
|
||||||
|
type: string
|
||||||
|
targets:
|
||||||
|
type: string
|
||||||
|
use_channels:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- asp_vols
|
||||||
|
- dis_vols
|
||||||
|
- reagent_sources
|
||||||
|
- targets
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: add_liquid参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommandAsync
|
||||||
|
auto-create_protocol:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
none_keys: []
|
||||||
|
protocol_author: null
|
||||||
|
protocol_date: null
|
||||||
|
protocol_description: null
|
||||||
|
protocol_name: null
|
||||||
|
protocol_type: null
|
||||||
|
protocol_version: null
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: create_protocol的参数schema
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
none_keys:
|
||||||
|
default: []
|
||||||
|
type: array
|
||||||
|
protocol_author:
|
||||||
|
type: string
|
||||||
|
protocol_date:
|
||||||
|
type: string
|
||||||
|
protocol_description:
|
||||||
|
type: string
|
||||||
|
protocol_name:
|
||||||
|
type: string
|
||||||
|
protocol_type:
|
||||||
|
type: string
|
||||||
|
protocol_version:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- protocol_name
|
||||||
|
- protocol_description
|
||||||
|
- protocol_version
|
||||||
|
- protocol_author
|
||||||
|
- protocol_date
|
||||||
|
- protocol_type
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: create_protocol参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommandAsync
|
||||||
|
auto-custom_delay:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
msg: null
|
||||||
|
seconds: 0
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: custom_delay的参数schema
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
msg:
|
||||||
|
type: string
|
||||||
|
seconds:
|
||||||
|
default: 0
|
||||||
|
type: string
|
||||||
|
required: []
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: custom_delay参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommandAsync
|
||||||
|
auto-iter_tips:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
tip_racks: null
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: iter_tips的参数schema
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
tip_racks:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- tip_racks
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: iter_tips参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-mix:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
height_to_bottom: null
|
||||||
|
mix_rate: null
|
||||||
|
mix_time: null
|
||||||
|
mix_vol: null
|
||||||
|
none_keys: []
|
||||||
|
offsets: null
|
||||||
|
targets: null
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: mix的参数schema
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
height_to_bottom:
|
||||||
|
type: string
|
||||||
|
mix_rate:
|
||||||
|
type: string
|
||||||
|
mix_time:
|
||||||
|
type: integer
|
||||||
|
mix_vol:
|
||||||
|
type: string
|
||||||
|
none_keys:
|
||||||
|
default: []
|
||||||
|
type: array
|
||||||
|
offsets:
|
||||||
|
type: string
|
||||||
|
targets:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- targets
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: mix参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommandAsync
|
||||||
|
auto-move_to:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
channel: 0
|
||||||
|
dis_to_top: 0
|
||||||
|
well: null
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: move_to的参数schema
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
channel:
|
||||||
|
default: 0
|
||||||
|
type: integer
|
||||||
|
dis_to_top:
|
||||||
|
default: 0
|
||||||
|
type: number
|
||||||
|
well:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- well
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: move_to参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommandAsync
|
||||||
|
auto-remove_liquid:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
blow_out_air_volume: null
|
||||||
|
delays: null
|
||||||
|
flow_rates: null
|
||||||
|
is_96_well: false
|
||||||
|
liquid_height: null
|
||||||
|
none_keys: []
|
||||||
|
offsets: null
|
||||||
|
sources: null
|
||||||
|
spread: wide
|
||||||
|
top: null
|
||||||
|
use_channels: null
|
||||||
|
vols: null
|
||||||
|
waste_liquid: null
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: remove_liquid的参数schema
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
blow_out_air_volume:
|
||||||
|
type: string
|
||||||
|
delays:
|
||||||
|
type: string
|
||||||
|
flow_rates:
|
||||||
|
type: string
|
||||||
|
is_96_well:
|
||||||
|
default: false
|
||||||
|
type: string
|
||||||
|
liquid_height:
|
||||||
|
type: string
|
||||||
|
none_keys:
|
||||||
|
default: []
|
||||||
|
type: array
|
||||||
|
offsets:
|
||||||
|
type: string
|
||||||
|
sources:
|
||||||
|
type: string
|
||||||
|
spread:
|
||||||
|
default: wide
|
||||||
|
type: string
|
||||||
|
top:
|
||||||
|
type: string
|
||||||
|
use_channels:
|
||||||
|
type: string
|
||||||
|
vols:
|
||||||
|
type: array
|
||||||
|
waste_liquid:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- vols
|
||||||
|
- sources
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: remove_liquid参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommandAsync
|
||||||
|
auto-run_protocol:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default: {}
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: run_protocol的参数schema
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties: {}
|
||||||
|
required: []
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: run_protocol参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommandAsync
|
||||||
|
auto-set_tiprack:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
tip_racks: null
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: set_tiprack的参数schema
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
tip_racks:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- tip_racks
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: set_tiprack参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-touch_tip:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
targets: null
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: touch_tip的参数schema
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
targets:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- targets
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: touch_tip参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommandAsync
|
||||||
|
auto-transfer_liquid:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
asp_flow_rates: null
|
||||||
|
asp_vols: null
|
||||||
|
blow_out_air_volume: null
|
||||||
|
delays: null
|
||||||
|
dis_flow_rates: null
|
||||||
|
dis_vols: null
|
||||||
|
is_96_well: false
|
||||||
|
liquid_height: null
|
||||||
|
mix_liquid_height: null
|
||||||
|
mix_rate: null
|
||||||
|
mix_stage: none
|
||||||
|
mix_times: null
|
||||||
|
mix_vol: null
|
||||||
|
none_keys: []
|
||||||
|
offsets: null
|
||||||
|
sources: null
|
||||||
|
spread: wide
|
||||||
|
targets: null
|
||||||
|
tip_racks: null
|
||||||
|
touch_tip: false
|
||||||
|
use_channels: null
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: transfer_liquid的参数schema
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
asp_flow_rates:
|
||||||
|
type: string
|
||||||
|
asp_vols:
|
||||||
|
type: string
|
||||||
|
blow_out_air_volume:
|
||||||
|
type: string
|
||||||
|
delays:
|
||||||
|
type: string
|
||||||
|
dis_flow_rates:
|
||||||
|
type: string
|
||||||
|
dis_vols:
|
||||||
|
type: string
|
||||||
|
is_96_well:
|
||||||
|
default: false
|
||||||
|
type: boolean
|
||||||
|
liquid_height:
|
||||||
|
type: string
|
||||||
|
mix_liquid_height:
|
||||||
|
type: string
|
||||||
|
mix_rate:
|
||||||
|
type: string
|
||||||
|
mix_stage:
|
||||||
|
default: none
|
||||||
|
type: string
|
||||||
|
mix_times:
|
||||||
|
type: string
|
||||||
|
mix_vol:
|
||||||
|
type: string
|
||||||
|
none_keys:
|
||||||
|
default: []
|
||||||
|
type: array
|
||||||
|
offsets:
|
||||||
|
type: string
|
||||||
|
sources:
|
||||||
|
type: string
|
||||||
|
spread:
|
||||||
|
default: wide
|
||||||
|
type: string
|
||||||
|
targets:
|
||||||
|
type: string
|
||||||
|
tip_racks:
|
||||||
|
type: string
|
||||||
|
touch_tip:
|
||||||
|
default: false
|
||||||
|
type: boolean
|
||||||
|
use_channels:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- sources
|
||||||
|
- targets
|
||||||
|
- tip_racks
|
||||||
|
- asp_vols
|
||||||
|
- dis_vols
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: transfer_liquid参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommandAsync
|
||||||
|
module: unilabos.devices.liquid_handling.prcxi.prcxi:PRCXI9300Handler
|
||||||
|
status_types: {}
|
||||||
|
type: python
|
||||||
|
description: prcxi液体处理器设备,基于pylabrobot控制
|
||||||
|
handles: []
|
||||||
|
icon: icon_yiyezhan.webp
|
||||||
|
init_param_schema:
|
||||||
|
config:
|
||||||
|
properties:
|
||||||
|
deck:
|
||||||
|
type: string
|
||||||
|
host:
|
||||||
|
type: string
|
||||||
|
port:
|
||||||
|
type: integer
|
||||||
|
setup:
|
||||||
|
default: true
|
||||||
|
type: string
|
||||||
|
timeout:
|
||||||
|
type: number
|
||||||
|
required:
|
||||||
|
- deck
|
||||||
|
- host
|
||||||
|
- port
|
||||||
|
- timeout
|
||||||
|
type: object
|
||||||
|
data:
|
||||||
|
properties: {}
|
||||||
|
required: []
|
||||||
|
type: object
|
||||||
liquid_handler.revvity:
|
liquid_handler.revvity:
|
||||||
class:
|
class:
|
||||||
action_value_mappings:
|
action_value_mappings:
|
||||||
|
|||||||
@@ -356,6 +356,85 @@ workstation:
|
|||||||
title: Add
|
title: Add
|
||||||
type: object
|
type: object
|
||||||
type: Add
|
type: Add
|
||||||
|
AdjustPHProtocol:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
ph_value: ph_value
|
||||||
|
reagent: reagent
|
||||||
|
vessel: vessel
|
||||||
|
goal_default:
|
||||||
|
ph_value: 0.0
|
||||||
|
reagent: ''
|
||||||
|
vessel: ''
|
||||||
|
handles:
|
||||||
|
input:
|
||||||
|
- data_key: vessel
|
||||||
|
data_source: handle
|
||||||
|
data_type: resource
|
||||||
|
handler_key: Vessel
|
||||||
|
label: Vessel
|
||||||
|
- data_key: reagent
|
||||||
|
data_source: handle
|
||||||
|
data_type: resource
|
||||||
|
handler_key: reagent
|
||||||
|
label: Reagent
|
||||||
|
output:
|
||||||
|
- data_key: vessel
|
||||||
|
data_source: executor
|
||||||
|
data_type: resource
|
||||||
|
handler_key: VesselOut
|
||||||
|
label: Vessel
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: ROS Action AdjustPH 的 JSON Schema
|
||||||
|
properties:
|
||||||
|
feedback:
|
||||||
|
description: Action 反馈 - 执行过程中从服务器发送到客户端
|
||||||
|
properties:
|
||||||
|
progress:
|
||||||
|
type: number
|
||||||
|
status:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- status
|
||||||
|
- progress
|
||||||
|
title: AdjustPH_Feedback
|
||||||
|
type: object
|
||||||
|
goal:
|
||||||
|
description: Action 目标 - 从客户端发送到服务器
|
||||||
|
properties:
|
||||||
|
ph_value:
|
||||||
|
type: number
|
||||||
|
reagent:
|
||||||
|
type: string
|
||||||
|
vessel:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- vessel
|
||||||
|
- ph_value
|
||||||
|
- reagent
|
||||||
|
title: AdjustPH_Goal
|
||||||
|
type: object
|
||||||
|
result:
|
||||||
|
description: Action 结果 - 完成后从服务器发送到客户端
|
||||||
|
properties:
|
||||||
|
message:
|
||||||
|
type: string
|
||||||
|
return_info:
|
||||||
|
type: string
|
||||||
|
success:
|
||||||
|
type: boolean
|
||||||
|
required:
|
||||||
|
- success
|
||||||
|
- message
|
||||||
|
- return_info
|
||||||
|
title: AdjustPH_Result
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: AdjustPH
|
||||||
|
type: object
|
||||||
|
type: AdjustPH
|
||||||
CentrifugeProtocol:
|
CentrifugeProtocol:
|
||||||
feedback: {}
|
feedback: {}
|
||||||
goal:
|
goal:
|
||||||
@@ -751,6 +830,75 @@ workstation:
|
|||||||
title: Dissolve
|
title: Dissolve
|
||||||
type: object
|
type: object
|
||||||
type: Dissolve
|
type: Dissolve
|
||||||
|
DryProtocol:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
compound: compound
|
||||||
|
vessel: vessel
|
||||||
|
goal_default:
|
||||||
|
compound: ''
|
||||||
|
vessel: ''
|
||||||
|
handles:
|
||||||
|
input:
|
||||||
|
- data_key: vessel
|
||||||
|
data_source: handle
|
||||||
|
data_type: resource
|
||||||
|
handler_key: Vessel
|
||||||
|
label: Vessel
|
||||||
|
output:
|
||||||
|
- data_key: vessel
|
||||||
|
data_source: executor
|
||||||
|
data_type: resource
|
||||||
|
handler_key: VesselOut
|
||||||
|
label: Vessel
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: ROS Action Dry 的 JSON Schema
|
||||||
|
properties:
|
||||||
|
feedback:
|
||||||
|
description: Action 反馈 - 执行过程中从服务器发送到客户端
|
||||||
|
properties:
|
||||||
|
progress:
|
||||||
|
type: number
|
||||||
|
status:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- status
|
||||||
|
- progress
|
||||||
|
title: Dry_Feedback
|
||||||
|
type: object
|
||||||
|
goal:
|
||||||
|
description: Action 目标 - 从客户端发送到服务器
|
||||||
|
properties:
|
||||||
|
compound:
|
||||||
|
type: string
|
||||||
|
vessel:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- compound
|
||||||
|
- vessel
|
||||||
|
title: Dry_Goal
|
||||||
|
type: object
|
||||||
|
result:
|
||||||
|
description: Action 结果 - 完成后从服务器发送到客户端
|
||||||
|
properties:
|
||||||
|
message:
|
||||||
|
type: string
|
||||||
|
return_info:
|
||||||
|
type: string
|
||||||
|
success:
|
||||||
|
type: boolean
|
||||||
|
required:
|
||||||
|
- success
|
||||||
|
- message
|
||||||
|
- return_info
|
||||||
|
title: Dry_Result
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: Dry
|
||||||
|
type: object
|
||||||
|
type: Dry
|
||||||
EvacuateAndRefillProtocol:
|
EvacuateAndRefillProtocol:
|
||||||
feedback: {}
|
feedback: {}
|
||||||
goal:
|
goal:
|
||||||
@@ -1399,6 +1547,80 @@ workstation:
|
|||||||
title: HeatChillStop
|
title: HeatChillStop
|
||||||
type: object
|
type: object
|
||||||
type: HeatChillStop
|
type: HeatChillStop
|
||||||
|
HydrogenateProtocol:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
temp: temp
|
||||||
|
time: time
|
||||||
|
vessel: vessel
|
||||||
|
goal_default:
|
||||||
|
temp: ''
|
||||||
|
time: ''
|
||||||
|
vessel: ''
|
||||||
|
handles:
|
||||||
|
input:
|
||||||
|
- data_key: vessel
|
||||||
|
data_source: handle
|
||||||
|
data_type: resource
|
||||||
|
handler_key: Vessel
|
||||||
|
label: Vessel
|
||||||
|
output:
|
||||||
|
- data_key: vessel
|
||||||
|
data_source: executor
|
||||||
|
data_type: resource
|
||||||
|
handler_key: VesselOut
|
||||||
|
label: Vessel
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: ROS Action Hydrogenate 的 JSON Schema
|
||||||
|
properties:
|
||||||
|
feedback:
|
||||||
|
description: Action 反馈 - 执行过程中从服务器发送到客户端
|
||||||
|
properties:
|
||||||
|
progress:
|
||||||
|
type: number
|
||||||
|
status:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- status
|
||||||
|
- progress
|
||||||
|
title: Hydrogenate_Feedback
|
||||||
|
type: object
|
||||||
|
goal:
|
||||||
|
description: Action 目标 - 从客户端发送到服务器
|
||||||
|
properties:
|
||||||
|
temp:
|
||||||
|
type: string
|
||||||
|
time:
|
||||||
|
type: string
|
||||||
|
vessel:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- temp
|
||||||
|
- time
|
||||||
|
- vessel
|
||||||
|
title: Hydrogenate_Goal
|
||||||
|
type: object
|
||||||
|
result:
|
||||||
|
description: Action 结果 - 完成后从服务器发送到客户端
|
||||||
|
properties:
|
||||||
|
message:
|
||||||
|
type: string
|
||||||
|
return_info:
|
||||||
|
type: string
|
||||||
|
success:
|
||||||
|
type: boolean
|
||||||
|
required:
|
||||||
|
- success
|
||||||
|
- message
|
||||||
|
- return_info
|
||||||
|
title: Hydrogenate_Result
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: Hydrogenate
|
||||||
|
type: object
|
||||||
|
type: Hydrogenate
|
||||||
PumpTransferProtocol:
|
PumpTransferProtocol:
|
||||||
feedback: {}
|
feedback: {}
|
||||||
goal:
|
goal:
|
||||||
@@ -1554,6 +1776,159 @@ workstation:
|
|||||||
title: PumpTransfer
|
title: PumpTransfer
|
||||||
type: object
|
type: object
|
||||||
type: PumpTransfer
|
type: PumpTransfer
|
||||||
|
RecrystallizeProtocol:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
ratio: ratio
|
||||||
|
solvent1: solvent1
|
||||||
|
solvent2: solvent2
|
||||||
|
vessel: vessel
|
||||||
|
volume: volume
|
||||||
|
goal_default:
|
||||||
|
ratio: ''
|
||||||
|
solvent1: ''
|
||||||
|
solvent2: ''
|
||||||
|
vessel: ''
|
||||||
|
volume: 0.0
|
||||||
|
handles:
|
||||||
|
input:
|
||||||
|
- data_key: vessel
|
||||||
|
data_source: handle
|
||||||
|
data_type: resource
|
||||||
|
handler_key: Vessel
|
||||||
|
label: Vessel
|
||||||
|
- data_key: solvent
|
||||||
|
data_source: handle
|
||||||
|
data_type: resource
|
||||||
|
handler_key: solvent1
|
||||||
|
label: Solvent 1
|
||||||
|
- data_key: solvent
|
||||||
|
data_source: handle
|
||||||
|
data_type: resource
|
||||||
|
handler_key: solvent2
|
||||||
|
label: Solvent 2
|
||||||
|
output:
|
||||||
|
- data_key: vessel
|
||||||
|
data_source: executor
|
||||||
|
data_type: resource
|
||||||
|
handler_key: VesselOut
|
||||||
|
label: Vessel
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: ROS Action Recrystallize 的 JSON Schema
|
||||||
|
properties:
|
||||||
|
feedback:
|
||||||
|
description: Action 反馈 - 执行过程中从服务器发送到客户端
|
||||||
|
properties:
|
||||||
|
progress:
|
||||||
|
type: number
|
||||||
|
status:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- status
|
||||||
|
- progress
|
||||||
|
title: Recrystallize_Feedback
|
||||||
|
type: object
|
||||||
|
goal:
|
||||||
|
description: Action 目标 - 从客户端发送到服务器
|
||||||
|
properties:
|
||||||
|
ratio:
|
||||||
|
type: string
|
||||||
|
solvent1:
|
||||||
|
type: string
|
||||||
|
solvent2:
|
||||||
|
type: string
|
||||||
|
vessel:
|
||||||
|
type: string
|
||||||
|
volume:
|
||||||
|
type: number
|
||||||
|
required:
|
||||||
|
- ratio
|
||||||
|
- solvent1
|
||||||
|
- solvent2
|
||||||
|
- vessel
|
||||||
|
- volume
|
||||||
|
title: Recrystallize_Goal
|
||||||
|
type: object
|
||||||
|
result:
|
||||||
|
description: Action 结果 - 完成后从服务器发送到客户端
|
||||||
|
properties:
|
||||||
|
message:
|
||||||
|
type: string
|
||||||
|
return_info:
|
||||||
|
type: string
|
||||||
|
success:
|
||||||
|
type: boolean
|
||||||
|
required:
|
||||||
|
- success
|
||||||
|
- message
|
||||||
|
- return_info
|
||||||
|
title: Recrystallize_Result
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: Recrystallize
|
||||||
|
type: object
|
||||||
|
type: Recrystallize
|
||||||
|
ResetHandlingProtocol:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
solvent: solvent
|
||||||
|
goal_default:
|
||||||
|
solvent: ''
|
||||||
|
handles:
|
||||||
|
input:
|
||||||
|
- data_key: solvent
|
||||||
|
data_source: handle
|
||||||
|
data_type: resource
|
||||||
|
handler_key: solvent
|
||||||
|
label: Solvent
|
||||||
|
output: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: ROS Action ResetHandling 的 JSON Schema
|
||||||
|
properties:
|
||||||
|
feedback:
|
||||||
|
description: Action 反馈 - 执行过程中从服务器发送到客户端
|
||||||
|
properties:
|
||||||
|
progress:
|
||||||
|
type: number
|
||||||
|
status:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- status
|
||||||
|
- progress
|
||||||
|
title: ResetHandling_Feedback
|
||||||
|
type: object
|
||||||
|
goal:
|
||||||
|
description: Action 目标 - 从客户端发送到服务器
|
||||||
|
properties:
|
||||||
|
solvent:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- solvent
|
||||||
|
title: ResetHandling_Goal
|
||||||
|
type: object
|
||||||
|
result:
|
||||||
|
description: Action 结果 - 完成后从服务器发送到客户端
|
||||||
|
properties:
|
||||||
|
message:
|
||||||
|
type: string
|
||||||
|
return_info:
|
||||||
|
type: string
|
||||||
|
success:
|
||||||
|
type: boolean
|
||||||
|
required:
|
||||||
|
- success
|
||||||
|
- message
|
||||||
|
- return_info
|
||||||
|
title: ResetHandling_Result
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: ResetHandling
|
||||||
|
type: object
|
||||||
|
type: ResetHandling
|
||||||
RunColumnProtocol:
|
RunColumnProtocol:
|
||||||
feedback: {}
|
feedback: {}
|
||||||
goal:
|
goal:
|
||||||
@@ -2355,381 +2730,6 @@ workstation:
|
|||||||
title: initialize_device参数
|
title: initialize_device参数
|
||||||
type: object
|
type: object
|
||||||
type: UniLabJsonCommand
|
type: UniLabJsonCommand
|
||||||
AdjustPHProtocol:
|
|
||||||
feedback: {}
|
|
||||||
goal:
|
|
||||||
vessel: vessel
|
|
||||||
ph_value: ph_value
|
|
||||||
reagent: reagent
|
|
||||||
goal_default:
|
|
||||||
vessel: ''
|
|
||||||
ph_value: 7.0
|
|
||||||
reagent: ''
|
|
||||||
handles:
|
|
||||||
input:
|
|
||||||
- data_key: vessel
|
|
||||||
data_source: handle
|
|
||||||
data_type: resource
|
|
||||||
handler_key: Vessel
|
|
||||||
label: Vessel
|
|
||||||
- data_key: reagent
|
|
||||||
data_source: handle
|
|
||||||
data_type: resource
|
|
||||||
handler_key: reagent
|
|
||||||
label: Reagent
|
|
||||||
output:
|
|
||||||
- data_key: vessel
|
|
||||||
data_source: executor
|
|
||||||
data_type: resource
|
|
||||||
handler_key: VesselOut
|
|
||||||
label: Vessel
|
|
||||||
result: {}
|
|
||||||
schema:
|
|
||||||
description: ROS Action AdjustPH 的 JSON Schema
|
|
||||||
properties:
|
|
||||||
feedback:
|
|
||||||
description: Action 反馈 - 执行过程中从服务器发送到客户端
|
|
||||||
properties:
|
|
||||||
status:
|
|
||||||
type: string
|
|
||||||
progress:
|
|
||||||
type: number
|
|
||||||
required:
|
|
||||||
- status
|
|
||||||
- progress
|
|
||||||
title: AdjustPH_Feedback
|
|
||||||
type: object
|
|
||||||
goal:
|
|
||||||
description: Action 目标 - 从客户端发送到服务器
|
|
||||||
properties:
|
|
||||||
vessel:
|
|
||||||
type: string
|
|
||||||
ph_value:
|
|
||||||
type: number
|
|
||||||
reagent:
|
|
||||||
type: string
|
|
||||||
required:
|
|
||||||
- vessel
|
|
||||||
- ph_value
|
|
||||||
- reagent
|
|
||||||
title: AdjustPH_Goal
|
|
||||||
type: object
|
|
||||||
result:
|
|
||||||
description: Action 结果 - 完成后从服务器发送到客户端
|
|
||||||
properties:
|
|
||||||
message:
|
|
||||||
type: string
|
|
||||||
return_info:
|
|
||||||
type: string
|
|
||||||
success:
|
|
||||||
type: boolean
|
|
||||||
required:
|
|
||||||
- success
|
|
||||||
- message
|
|
||||||
- return_info
|
|
||||||
title: AdjustPH_Result
|
|
||||||
type: object
|
|
||||||
required:
|
|
||||||
- goal
|
|
||||||
title: AdjustPH
|
|
||||||
type: object
|
|
||||||
type: AdjustPH
|
|
||||||
ResetHandlingProtocol:
|
|
||||||
feedback: {}
|
|
||||||
goal:
|
|
||||||
solvent: solvent
|
|
||||||
goal_default:
|
|
||||||
solvent: ''
|
|
||||||
handles:
|
|
||||||
input:
|
|
||||||
- data_key: solvent
|
|
||||||
data_source: handle
|
|
||||||
data_type: resource
|
|
||||||
handler_key: solvent
|
|
||||||
label: Solvent
|
|
||||||
output: []
|
|
||||||
result: {}
|
|
||||||
schema:
|
|
||||||
description: ROS Action ResetHandling 的 JSON Schema
|
|
||||||
properties:
|
|
||||||
feedback:
|
|
||||||
description: Action 反馈 - 执行过程中从服务器发送到客户端
|
|
||||||
properties:
|
|
||||||
status:
|
|
||||||
type: string
|
|
||||||
progress:
|
|
||||||
type: number
|
|
||||||
required:
|
|
||||||
- status
|
|
||||||
- progress
|
|
||||||
title: ResetHandling_Feedback
|
|
||||||
type: object
|
|
||||||
goal:
|
|
||||||
description: Action 目标 - 从客户端发送到服务器
|
|
||||||
properties:
|
|
||||||
solvent:
|
|
||||||
type: string
|
|
||||||
required:
|
|
||||||
- solvent
|
|
||||||
title: ResetHandling_Goal
|
|
||||||
type: object
|
|
||||||
result:
|
|
||||||
description: Action 结果 - 完成后从服务器发送到客户端
|
|
||||||
properties:
|
|
||||||
message:
|
|
||||||
type: string
|
|
||||||
return_info:
|
|
||||||
type: string
|
|
||||||
success:
|
|
||||||
type: boolean
|
|
||||||
required:
|
|
||||||
- success
|
|
||||||
- message
|
|
||||||
- return_info
|
|
||||||
title: ResetHandling_Result
|
|
||||||
type: object
|
|
||||||
required:
|
|
||||||
- goal
|
|
||||||
title: ResetHandling
|
|
||||||
type: object
|
|
||||||
type: ResetHandling
|
|
||||||
DryProtocol:
|
|
||||||
feedback: {}
|
|
||||||
goal:
|
|
||||||
compound: compound
|
|
||||||
vessel: vessel
|
|
||||||
goal_default:
|
|
||||||
compound: ''
|
|
||||||
vessel: ''
|
|
||||||
handles:
|
|
||||||
input:
|
|
||||||
- data_key: vessel
|
|
||||||
data_source: handle
|
|
||||||
data_type: resource
|
|
||||||
handler_key: Vessel
|
|
||||||
label: Vessel
|
|
||||||
output:
|
|
||||||
- data_key: vessel
|
|
||||||
data_source: executor
|
|
||||||
data_type: resource
|
|
||||||
handler_key: VesselOut
|
|
||||||
label: Vessel
|
|
||||||
result: {}
|
|
||||||
schema:
|
|
||||||
description: ROS Action Dry 的 JSON Schema
|
|
||||||
properties:
|
|
||||||
feedback:
|
|
||||||
description: Action 反馈 - 执行过程中从服务器发送到客户端
|
|
||||||
properties:
|
|
||||||
status:
|
|
||||||
type: string
|
|
||||||
progress:
|
|
||||||
type: number
|
|
||||||
required:
|
|
||||||
- status
|
|
||||||
- progress
|
|
||||||
title: Dry_Feedback
|
|
||||||
type: object
|
|
||||||
goal:
|
|
||||||
description: Action 目标 - 从客户端发送到服务器
|
|
||||||
properties:
|
|
||||||
compound:
|
|
||||||
type: string
|
|
||||||
vessel:
|
|
||||||
type: string
|
|
||||||
required:
|
|
||||||
- compound
|
|
||||||
- vessel
|
|
||||||
title: Dry_Goal
|
|
||||||
type: object
|
|
||||||
result:
|
|
||||||
description: Action 结果 - 完成后从服务器发送到客户端
|
|
||||||
properties:
|
|
||||||
message:
|
|
||||||
type: string
|
|
||||||
return_info:
|
|
||||||
type: string
|
|
||||||
success:
|
|
||||||
type: boolean
|
|
||||||
required:
|
|
||||||
- success
|
|
||||||
- message
|
|
||||||
- return_info
|
|
||||||
title: Dry_Result
|
|
||||||
type: object
|
|
||||||
required:
|
|
||||||
- goal
|
|
||||||
title: Dry
|
|
||||||
type: object
|
|
||||||
type: Dry
|
|
||||||
HydrogenateProtocol:
|
|
||||||
feedback: {}
|
|
||||||
goal:
|
|
||||||
temp: temp
|
|
||||||
time: time
|
|
||||||
vessel: vessel
|
|
||||||
goal_default:
|
|
||||||
temp: ''
|
|
||||||
time: ''
|
|
||||||
vessel: ''
|
|
||||||
handles:
|
|
||||||
input:
|
|
||||||
- data_key: vessel
|
|
||||||
data_source: handle
|
|
||||||
data_type: resource
|
|
||||||
handler_key: Vessel
|
|
||||||
label: Vessel
|
|
||||||
output:
|
|
||||||
- data_key: vessel
|
|
||||||
data_source: executor
|
|
||||||
data_type: resource
|
|
||||||
handler_key: VesselOut
|
|
||||||
label: Vessel
|
|
||||||
result: {}
|
|
||||||
schema:
|
|
||||||
description: ROS Action Hydrogenate 的 JSON Schema
|
|
||||||
properties:
|
|
||||||
feedback:
|
|
||||||
description: Action 反馈 - 执行过程中从服务器发送到客户端
|
|
||||||
properties:
|
|
||||||
status:
|
|
||||||
type: string
|
|
||||||
progress:
|
|
||||||
type: number
|
|
||||||
required:
|
|
||||||
- status
|
|
||||||
- progress
|
|
||||||
title: Hydrogenate_Feedback
|
|
||||||
type: object
|
|
||||||
goal:
|
|
||||||
description: Action 目标 - 从客户端发送到服务器
|
|
||||||
properties:
|
|
||||||
temp:
|
|
||||||
type: string
|
|
||||||
time:
|
|
||||||
type: string
|
|
||||||
vessel:
|
|
||||||
type: string
|
|
||||||
required:
|
|
||||||
- temp
|
|
||||||
- time
|
|
||||||
- vessel
|
|
||||||
title: Hydrogenate_Goal
|
|
||||||
type: object
|
|
||||||
result:
|
|
||||||
description: Action 结果 - 完成后从服务器发送到客户端
|
|
||||||
properties:
|
|
||||||
message:
|
|
||||||
type: string
|
|
||||||
return_info:
|
|
||||||
type: string
|
|
||||||
success:
|
|
||||||
type: boolean
|
|
||||||
required:
|
|
||||||
- success
|
|
||||||
- message
|
|
||||||
- return_info
|
|
||||||
title: Hydrogenate_Result
|
|
||||||
type: object
|
|
||||||
required:
|
|
||||||
- goal
|
|
||||||
title: Hydrogenate
|
|
||||||
type: object
|
|
||||||
type: Hydrogenate
|
|
||||||
RecrystallizeProtocol:
|
|
||||||
feedback: {}
|
|
||||||
goal:
|
|
||||||
ratio: ratio
|
|
||||||
solvent1: solvent1
|
|
||||||
solvent2: solvent2
|
|
||||||
vessel: vessel
|
|
||||||
volume: volume
|
|
||||||
goal_default:
|
|
||||||
ratio: ''
|
|
||||||
solvent1: ''
|
|
||||||
solvent2: ''
|
|
||||||
vessel: ''
|
|
||||||
volume: 0.0
|
|
||||||
handles:
|
|
||||||
input:
|
|
||||||
- data_key: vessel
|
|
||||||
data_source: handle
|
|
||||||
data_type: resource
|
|
||||||
handler_key: Vessel
|
|
||||||
label: Vessel
|
|
||||||
- data_key: solvent
|
|
||||||
data_source: handle
|
|
||||||
data_type: resource
|
|
||||||
handler_key: solvent1
|
|
||||||
label: Solvent 1
|
|
||||||
- data_key: solvent
|
|
||||||
data_source: handle
|
|
||||||
data_type: resource
|
|
||||||
handler_key: solvent2
|
|
||||||
label: Solvent 2
|
|
||||||
output:
|
|
||||||
- data_key: vessel
|
|
||||||
data_source: executor
|
|
||||||
data_type: resource
|
|
||||||
handler_key: VesselOut
|
|
||||||
label: Vessel
|
|
||||||
result: {}
|
|
||||||
schema:
|
|
||||||
description: ROS Action Recrystallize 的 JSON Schema
|
|
||||||
properties:
|
|
||||||
feedback:
|
|
||||||
description: Action 反馈 - 执行过程中从服务器发送到客户端
|
|
||||||
properties:
|
|
||||||
status:
|
|
||||||
type: string
|
|
||||||
progress:
|
|
||||||
type: number
|
|
||||||
required:
|
|
||||||
- status
|
|
||||||
- progress
|
|
||||||
title: Recrystallize_Feedback
|
|
||||||
type: object
|
|
||||||
goal:
|
|
||||||
description: Action 目标 - 从客户端发送到服务器
|
|
||||||
properties:
|
|
||||||
ratio:
|
|
||||||
type: string
|
|
||||||
solvent1:
|
|
||||||
type: string
|
|
||||||
solvent2:
|
|
||||||
type: string
|
|
||||||
vessel:
|
|
||||||
type: string
|
|
||||||
volume:
|
|
||||||
type: number
|
|
||||||
required:
|
|
||||||
- ratio
|
|
||||||
- solvent1
|
|
||||||
- solvent2
|
|
||||||
- vessel
|
|
||||||
- volume
|
|
||||||
title: Recrystallize_Goal
|
|
||||||
type: object
|
|
||||||
result:
|
|
||||||
description: Action 结果 - 完成后从服务器发送到客户端
|
|
||||||
properties:
|
|
||||||
message:
|
|
||||||
type: string
|
|
||||||
return_info:
|
|
||||||
type: string
|
|
||||||
success:
|
|
||||||
type: boolean
|
|
||||||
required:
|
|
||||||
- success
|
|
||||||
- message
|
|
||||||
- return_info
|
|
||||||
title: Recrystallize_Result
|
|
||||||
type: object
|
|
||||||
required:
|
|
||||||
- goal
|
|
||||||
title: Recrystallize
|
|
||||||
type: object
|
|
||||||
type: Recrystallize
|
|
||||||
module: unilabos.ros.nodes.presets.protocol_node:ROS2ProtocolNode
|
module: unilabos.ros.nodes.presets.protocol_node:ROS2ProtocolNode
|
||||||
status_types: {}
|
status_types: {}
|
||||||
type: ros2
|
type: ros2
|
||||||
|
|||||||
@@ -923,11 +923,18 @@ class ROS2DeviceNode:
|
|||||||
driver_class.__module__.startswith("pylabrobot")
|
driver_class.__module__.startswith("pylabrobot")
|
||||||
or driver_class.__name__ == "LiquidHandlerAbstract"
|
or driver_class.__name__ == "LiquidHandlerAbstract"
|
||||||
or driver_class.__name__ == "LiquidHandlerBiomek"
|
or driver_class.__name__ == "LiquidHandlerBiomek"
|
||||||
|
or driver_class.__name__ == "PRCXI9300Handler"
|
||||||
)
|
)
|
||||||
|
|
||||||
# TODO: 要在创建之前预先请求服务器是否有当前id的物料,放到resource_tracker中,让pylabrobot进行创建
|
# TODO: 要在创建之前预先请求服务器是否有当前id的物料,放到resource_tracker中,让pylabrobot进行创建
|
||||||
# 创建设备类实例
|
# 创建设备类实例
|
||||||
if use_pylabrobot_creator:
|
if use_pylabrobot_creator:
|
||||||
|
# 先对pylabrobot的子资源进行加载,不然subclass无法认出
|
||||||
|
# 在下方对于加载Deck等Resource要手动import
|
||||||
|
# noinspection PyUnresolvedReferences
|
||||||
|
from unilabos.devices.liquid_handling.prcxi.prcxi import PRCXI9300Deck
|
||||||
|
# noinspection PyUnresolvedReferences
|
||||||
|
from unilabos.devices.liquid_handling.prcxi.prcxi import PRCXI9300Container
|
||||||
self._driver_creator = PyLabRobotCreator(
|
self._driver_creator = PyLabRobotCreator(
|
||||||
driver_class, children=children, resource_tracker=self.resource_tracker
|
driver_class, children=children, resource_tracker=self.resource_tracker
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -148,7 +148,7 @@ class PyLabRobotCreator(DeviceClassCreator[T]):
|
|||||||
contain_model = not issubclass(target_type, Deck)
|
contain_model = not issubclass(target_type, Deck)
|
||||||
resource, target_type = self._process_resource_mapping(resource, target_type)
|
resource, target_type = self._process_resource_mapping(resource, target_type)
|
||||||
resource_instance: Resource = resource_ulab_to_plr(resource, contain_model)
|
resource_instance: Resource = resource_ulab_to_plr(resource, contain_model)
|
||||||
|
states[prefix_path] = resource_instance.serialize_all_state()
|
||||||
# 使用 prefix_path 作为 key 存储资源状态
|
# 使用 prefix_path 作为 key 存储资源状态
|
||||||
if to_dict:
|
if to_dict:
|
||||||
serialized = resource_instance.serialize()
|
serialized = resource_instance.serialize()
|
||||||
@@ -199,7 +199,7 @@ class PyLabRobotCreator(DeviceClassCreator[T]):
|
|||||||
spect = inspect.signature(deserialize_method)
|
spect = inspect.signature(deserialize_method)
|
||||||
spec_args = spect.parameters
|
spec_args = spect.parameters
|
||||||
for param_name, param_value in data.copy().items():
|
for param_name, param_value in data.copy().items():
|
||||||
if "_resource_child_name" in param_value and "_resource_type" not in param_value:
|
if isinstance(param_value, dict) and "_resource_child_name" in param_value and "_resource_type" not in param_value:
|
||||||
arg_value = spec_args[param_name].annotation
|
arg_value = spec_args[param_name].annotation
|
||||||
data[param_name]["_resource_type"] = self.device_cls.__module__ + ":" + arg_value
|
data[param_name]["_resource_type"] = self.device_cls.__module__ + ":" + arg_value
|
||||||
logger.debug(f"自动补充 _resource_type: {data[param_name]['_resource_type']}")
|
logger.debug(f"自动补充 _resource_type: {data[param_name]['_resource_type']}")
|
||||||
@@ -230,7 +230,7 @@ class PyLabRobotCreator(DeviceClassCreator[T]):
|
|||||||
spect = inspect.signature(self.device_cls.__init__)
|
spect = inspect.signature(self.device_cls.__init__)
|
||||||
spec_args = spect.parameters
|
spec_args = spect.parameters
|
||||||
for param_name, param_value in data.copy().items():
|
for param_name, param_value in data.copy().items():
|
||||||
if "_resource_child_name" in param_value and "_resource_type" not in param_value:
|
if isinstance(param_value, dict) and "_resource_child_name" in param_value and "_resource_type" not in param_value:
|
||||||
arg_value = spec_args[param_name].annotation
|
arg_value = spec_args[param_name].annotation
|
||||||
data[param_name]["_resource_type"] = self.device_cls.__module__ + ":" + arg_value
|
data[param_name]["_resource_type"] = self.device_cls.__module__ + ":" + arg_value
|
||||||
logger.debug(f"自动补充 _resource_type: {data[param_name]['_resource_type']}")
|
logger.debug(f"自动补充 _resource_type: {data[param_name]['_resource_type']}")
|
||||||
|
|||||||
Reference in New Issue
Block a user