更新中析仪器,以及启动示例

This commit is contained in:
Xuwznln
2025-07-06 18:39:40 +08:00
parent bef44b2293
commit ce8667f937
7 changed files with 1577 additions and 454 deletions

190
test/experiments/prcxi.json Normal file
View 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": []
}

View File

@@ -183,7 +183,7 @@ class LiquidHandlerAbstract(LiquidHandler):
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_times: Optional[List[int]] = None,
mix_vol: Optional[int] = None,
mix_rate: Optional[int] = None,
mix_liquid_height: Optional[float] = None,

View File

@@ -1,17 +1,456 @@
import socket, json, contextlib
from typing import Any, List, Dict, Optional
import collections
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):
"""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._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
def _len_prefix(n: int) -> bytes:
@@ -30,17 +469,30 @@ class PRCXI9300:
if not chunk:
break
if first:
chunk, first = chunk[8:], False
chunk, first = chunk[8:], False
chunks.append(chunk)
return b"".join(chunks).decode()
def _call(self, service: str, method: str,
params: Optional[list] = None) -> Any:
# ---------------------------------------------------- 方案相关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 call(self, service: str, method: str, params: Optional[list] = None) -> Any:
payload = json.dumps(
{"ServiceName": service,
"MethodName": method,
"Paramters": params or []},
separators=(",", ":")
{"ServiceName": service, "MethodName": method, "Paramters": params or []}, separators=(",", ":")
)
resp = json.loads(self._raw_request(payload))
if not resp.get("Success", False):
@@ -51,87 +503,53 @@ class PRCXI9300:
except (TypeError, json.JSONDecodeError):
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:
"""Pause"""
return self._call("IAutomation", "Pause")
return self.call("IAutomation", "Pause")
def resume(self) -> bool:
"""Resume"""
return self._call("IAutomation", "Resume")
return self.call("IAutomation", "Resume")
def get_error_code(self) -> Optional[str]:
"""GetErrorCode"""
return self._call("IAutomation", "GetErrorCode")
return self.call("IAutomation", "GetErrorCode")
def clear_error_code(self) -> bool:
"""RemoveErrorCodet"""
return self._call("IAutomation", "RemoveErrorCodet")
return self.call("IAutomation", "RemoveErrorCodet")
# ---------------------------------------------------- 运行状态IMachineState
def step_state_list(self) -> List[Dict[str, Any]]:
"""GetStepStateList"""
return self._call("IMachineState", "GetStepStateList")
return self.call("IMachineState", "GetStepStateList")
def step_status(self, seq_num: int) -> Dict[str, Any]:
"""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]:
"""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]:
"""GetLocation"""
return self._call("IMachineState", "GetLocation", [axis_num])
return self.call("IMachineState", "GetLocation", [axis_num])
# ---------------------------------------------------- 版位矩阵IMatrix
def list_matrices(self) -> List[Dict[str, Any]]:
"""GetWorkTabletMatrices"""
return self._call("IMatrix", "GetWorkTabletMatrices")
return self.call("IMatrix", "GetWorkTabletMatrices")
def matrix_by_id(self, matrix_id: str) -> Dict[str, Any]:
"""GetWorkTabletMatrixById"""
return self._call("IMatrix", "GetWorkTabletMatrixById", [matrix_id])
def add_WorkTablet_Matrix(self,matrix):
return self._call("IMatrix", "AddWorkTabletMatrix", [matrix])
return self.call("IMatrix", "GetWorkTabletMatrixById", [matrix_id])
# ---------------------------------------------------- 一键运行
def run_solution(self, solution_id: str, channel_idx: int = 1) -> None:
self.load_solution(solution_id)
self.start(channel_idx)
# ---------------------------------------------------- 单点动作
def add_WorkTablet_Matrix(self, matrix: MatrixInfo):
return self.call("IMatrix", "AddWorkTabletMatrix", [matrix])
def Load(
self,
self,
axis: str,
dosage: int,
plate_no: int,
@@ -147,7 +565,7 @@ class PRCXI9300:
assist_fun3: str = "",
assist_fun4: str = "",
assist_fun5: str = "",
liquid_method: str = "NormalDispense"
liquid_method: str = "NormalDispense",
) -> Dict[str, Any]:
return {
"StepAxis": axis,
@@ -166,11 +584,11 @@ class PRCXI9300:
"AssistFun4": assist_fun4,
"AssistFun5": assist_fun5,
"HoleNumbers": hole_numbers,
"LiquidDispensingMethod": liquid_method
"LiquidDispensingMethod": liquid_method,
}
def Imbibing(
self,
self,
axis: str,
dosage: int,
plate_no: int,
@@ -186,7 +604,7 @@ class PRCXI9300:
assist_fun3: str = "",
assist_fun4: str = "",
assist_fun5: str = "",
liquid_method: str = "NormalDispense"
liquid_method: str = "NormalDispense",
) -> Dict[str, Any]:
return {
"StepAxis": axis,
@@ -205,12 +623,11 @@ class PRCXI9300:
"AssistFun4": assist_fun4,
"AssistFun5": assist_fun5,
"HoleNumbers": hole_numbers,
"LiquidDispensingMethod": liquid_method
"LiquidDispensingMethod": liquid_method,
}
def Tapping(
self,
self,
axis: str,
dosage: int,
plate_no: int,
@@ -226,7 +643,7 @@ class PRCXI9300:
assist_fun3: str = "",
assist_fun4: str = "",
assist_fun5: str = "",
liquid_method: str = "NormalDispense"
liquid_method: str = "NormalDispense",
) -> Dict[str, Any]:
return {
"StepAxis": axis,
@@ -245,12 +662,11 @@ class PRCXI9300:
"AssistFun4": assist_fun4,
"AssistFun5": assist_fun5,
"HoleNumbers": hole_numbers,
"LiquidDispensingMethod": liquid_method
"LiquidDispensingMethod": liquid_method,
}
def Blending(
self,
self,
axis: str,
dosage: int,
plate_no: int,
@@ -266,7 +682,7 @@ class PRCXI9300:
assist_fun3: str = "",
assist_fun4: str = "",
assist_fun5: str = "",
liquid_method: str = "NormalDispense"
liquid_method: str = "NormalDispense",
) -> Dict[str, Any]:
return {
"StepAxis": axis,
@@ -285,11 +701,11 @@ class PRCXI9300:
"AssistFun4": assist_fun4,
"AssistFun5": assist_fun5,
"HoleNumbers": hole_numbers,
"LiquidDispensingMethod": liquid_method
"LiquidDispensingMethod": liquid_method,
}
def UnLoad(
self,
self,
axis: str,
dosage: int,
plate_no: int,
@@ -305,7 +721,7 @@ class PRCXI9300:
assist_fun3: str = "",
assist_fun4: str = "",
assist_fun5: str = "",
liquid_method: str = "NormalDispense"
liquid_method: str = "NormalDispense",
) -> Dict[str, Any]:
return {
"StepAxis": axis,
@@ -324,5 +740,5 @@ class PRCXI9300:
"AssistFun4": assist_fun4,
"AssistFun5": assist_fun5,
"HoleNumbers": hole_numbers,
"LiquidDispensingMethod": liquid_method
"LiquidDispensingMethod": liquid_method,
}

View File

@@ -6064,6 +6064,516 @@ liquid_handler.biomek:
required:
- success
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:
class:
action_value_mappings:

View File

@@ -356,6 +356,85 @@ workstation:
title: Add
type: object
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:
feedback: {}
goal:
@@ -751,6 +830,75 @@ workstation:
title: Dissolve
type: object
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:
feedback: {}
goal:
@@ -1399,6 +1547,80 @@ workstation:
title: HeatChillStop
type: object
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:
feedback: {}
goal:
@@ -1554,6 +1776,159 @@ workstation:
title: PumpTransfer
type: object
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:
feedback: {}
goal:
@@ -2355,381 +2730,6 @@ workstation:
title: initialize_device参数
type: object
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
status_types: {}
type: ros2

View File

@@ -923,11 +923,18 @@ class ROS2DeviceNode:
driver_class.__module__.startswith("pylabrobot")
or driver_class.__name__ == "LiquidHandlerAbstract"
or driver_class.__name__ == "LiquidHandlerBiomek"
or driver_class.__name__ == "PRCXI9300Handler"
)
# TODO: 要在创建之前预先请求服务器是否有当前id的物料放到resource_tracker中让pylabrobot进行创建
# 创建设备类实例
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(
driver_class, children=children, resource_tracker=self.resource_tracker
)

View File

@@ -148,7 +148,7 @@ class PyLabRobotCreator(DeviceClassCreator[T]):
contain_model = not issubclass(target_type, Deck)
resource, target_type = self._process_resource_mapping(resource, target_type)
resource_instance: Resource = resource_ulab_to_plr(resource, contain_model)
states[prefix_path] = resource_instance.serialize_all_state()
# 使用 prefix_path 作为 key 存储资源状态
if to_dict:
serialized = resource_instance.serialize()
@@ -199,7 +199,7 @@ class PyLabRobotCreator(DeviceClassCreator[T]):
spect = inspect.signature(deserialize_method)
spec_args = spect.parameters
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
data[param_name]["_resource_type"] = self.device_cls.__module__ + ":" + arg_value
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__)
spec_args = spect.parameters
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
data[param_name]["_resource_type"] = self.device_cls.__module__ + ":" + arg_value
logger.debug(f"自动补充 _resource_type: {data[param_name]['_resource_type']}")