Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
168 changes: 149 additions & 19 deletions unilabos/devices/liquid_handling/prcxi/prcxi.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import asyncio
import collections
from collections import OrderedDict
import contextlib
import json
import os
import socket
import time
import uuid
from typing import Any, List, Dict, Optional, Tuple, TypedDict, Union, Sequence, Iterator, Literal
from pylabrobot.liquid_handling.standard import GripDirection

Expand All @@ -28,7 +30,7 @@
ResourceMove,
ResourceDrop,
)
from pylabrobot.resources import ResourceHolder, ResourceStack, Tip, Deck, Plate, Well, TipRack, Resource, Container, Coordinate, TipSpot, Trash
from pylabrobot.resources import ResourceHolder, ResourceStack, Tip, Deck, Plate, Well, TipRack, Resource, Container, Coordinate, TipSpot, Trash, PlateAdapter, TubeRack

from unilabos.devices.liquid_handling.liquid_handler_abstract import LiquidHandlerAbstract, SimpleReturn
from unilabos.ros.nodes.base_device_node import BaseROS2DeviceNode
Expand Down Expand Up @@ -69,7 +71,35 @@ class PRCXI9300Deck(Deck):
def __init__(self, name: str, size_x: float, size_y: float, size_z: float, **kwargs):
super().__init__(name, size_x, size_y, size_z)
self.slots = [None] * 6 # PRCXI 9300 有 6 个槽位
class PRCXI9300Container(Plate):
"""PRCXI 9300 的专用 Container 类,继承自 Plate,用于槽位定位和未知模块。

该类定义了 PRCXI 9300 的工作台布局和槽位信息。
"""

def __init__(
self,
name: str,
size_x: float,
size_y: float,
size_z: float,
category: str,
ordering: collections.OrderedDict,
model: Optional[str] = None,
**kwargs,
):
super().__init__(name, size_x, size_y, size_z, category=category, ordering=ordering, model=model)
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 PRCXI9300Plate(Plate):
"""
专用孔板类:
Expand All @@ -83,11 +113,43 @@ def __init__(self, name: str, size_x: float, size_y: float, size_z: float,
model: Optional[str] = None,
material_info: Optional[Dict[str, Any]] = None,
**kwargs):
items = ordered_items if ordered_items is not None else ordering
super().__init__(name, size_x, size_y, size_z,
ordered_items=items,
category=category,
model=model, **kwargs)
# 如果 ordered_items 不为 None,直接使用
if ordered_items is not None:
items = ordered_items
elif ordering is not None:
# 检查 ordering 中的值是否是字符串(从 JSON 反序列化时的情况)
# 如果是字符串,说明这是位置名称,需要让 Plate 自己创建 Well 对象
# 我们只传递位置信息(键),不传递值,使用 ordering 参数
if ordering and isinstance(next(iter(ordering.values()), None), str):
# ordering 的值是字符串,只使用键(位置信息)创建新的 OrderedDict
# 传递 ordering 参数而不是 ordered_items,让 Plate 自己创建 Well 对象
items = None
# 使用 ordering 参数,只包含位置信息(键)
ordering_param = collections.OrderedDict((k, None) for k in ordering.keys())
else:
# ordering 的值已经是对象,可以直接使用
items = ordering
ordering_param = None
else:
items = None
ordering_param = None

# 根据情况传递不同的参数
if items is not None:
super().__init__(name, size_x, size_y, size_z,
ordered_items=items,
category=category,
model=model, **kwargs)
elif ordering_param is not None:
# 传递 ordering 参数,让 Plate 自己创建 Well 对象
super().__init__(name, size_x, size_y, size_z,
ordering=ordering_param,
category=category,
model=model, **kwargs)
else:
super().__init__(name, size_x, size_y, size_z,
category=category,
model=model, **kwargs)

self._unilabos_state = {}
if material_info:
Expand Down Expand Up @@ -124,8 +186,7 @@ def serialize_state(self) -> Dict[str, Dict[str, Any]]:
safe_state[k] = v

data.update(safe_state)
return data

return data # 其他顶层属性也进行类型检查
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpick: Inline comment after return is misleading given the current implementation.

The # 其他顶层属性也进行类型检查 comment implies more top-level type checks than are actually performed (only _unilabos_state is filtered before return). Consider either removing/rewording this comment to match the behavior, or adding the missing checks if they’re required.

class PRCXI9300TipRack(TipRack):
""" 专用吸头盒类 """
def __init__(self, name: str, size_x: float, size_y: float, size_z: float,
Expand All @@ -135,11 +196,43 @@ def __init__(self, name: str, size_x: float, size_y: float, size_z: float,
model: Optional[str] = None,
material_info: Optional[Dict[str, Any]] = None,
**kwargs):
items = ordered_items if ordered_items is not None else ordering
super().__init__(name, size_x, size_y, size_z,
ordered_items=items,
category=category,
model=model, **kwargs)
# 如果 ordered_items 不为 None,直接使用
if ordered_items is not None:
items = ordered_items
elif ordering is not None:
# 检查 ordering 中的值是否是字符串(从 JSON 反序列化时的情况)
# 如果是字符串,说明这是位置名称,需要让 TipRack 自己创建 Tip 对象
# 我们只传递位置信息(键),不传递值,使用 ordering 参数
if ordering and isinstance(next(iter(ordering.values()), None), str):
# ordering 的值是字符串,只使用键(位置信息)创建新的 OrderedDict
# 传递 ordering 参数而不是 ordered_items,让 TipRack 自己创建 Tip 对象
items = None
# 使用 ordering 参数,只包含位置信息(键)
ordering_param = collections.OrderedDict((k, None) for k in ordering.keys())
else:
# ordering 的值已经是对象,可以直接使用
items = ordering
ordering_param = None
else:
items = None
ordering_param = None

# 根据情况传递不同的参数
if items is not None:
super().__init__(name, size_x, size_y, size_z,
ordered_items=items,
category=category,
model=model, **kwargs)
elif ordering_param is not None:
# 传递 ordering 参数,让 TipRack 自己创建 Tip 对象
super().__init__(name, size_x, size_y, size_z,
ordering=ordering_param,
category=category,
model=model, **kwargs)
else:
super().__init__(name, size_x, size_y, size_z,
category=category,
model=model, **kwargs)
self._unilabos_state = {}
if material_info:
self._unilabos_state["Material"] = material_info
Expand Down Expand Up @@ -235,16 +328,53 @@ def __init__(self, name: str, size_x: float, size_y: float, size_z: float,
category: str = "tube_rack",
items: Optional[Dict[str, Any]] = None,
ordered_items: Optional[OrderedDict] = None,
ordering: Optional[OrderedDict] = None,
model: Optional[str] = None,
material_info: Optional[Dict[str, Any]] = None,
**kwargs):

# 兼容处理:PLR 的 TubeRack 构造函数可能接受 items 或 ordered_items
items_to_pass = items if items is not None else ordered_items
super().__init__(name, size_x, size_y, size_z,
ordered_items=ordered_items,
model=model,
**kwargs)
# 如果 ordered_items 不为 None,直接使用
if ordered_items is not None:
items_to_pass = ordered_items
ordering_param = None
elif ordering is not None:
# 检查 ordering 中的值是否是字符串(从 JSON 反序列化时的情况)
# 如果是字符串,说明这是位置名称,需要让 TubeRack 自己创建 Tube 对象
# 我们只传递位置信息(键),不传递值,使用 ordering 参数
if ordering and isinstance(next(iter(ordering.values()), None), str):
# ordering 的值是字符串,只使用键(位置信息)创建新的 OrderedDict
# 传递 ordering 参数而不是 ordered_items,让 TubeRack 自己创建 Tube 对象
items_to_pass = None
# 使用 ordering 参数,只包含位置信息(键)
ordering_param = collections.OrderedDict((k, None) for k in ordering.keys())
else:
# ordering 的值已经是对象,可以直接使用
items_to_pass = ordering
ordering_param = None
elif items is not None:
# 兼容旧的 items 参数
items_to_pass = items
ordering_param = None
else:
items_to_pass = None
ordering_param = None

# 根据情况传递不同的参数
if items_to_pass is not None:
super().__init__(name, size_x, size_y, size_z,
ordered_items=items_to_pass,
model=model,
**kwargs)
elif ordering_param is not None:
# 传递 ordering 参数,让 TubeRack 自己创建 Tube 对象
super().__init__(name, size_x, size_y, size_z,
ordering=ordering_param,
model=model,
**kwargs)
else:
super().__init__(name, size_x, size_y, size_z,
model=model,
**kwargs)

self._unilabos_state = {}
if material_info:
Expand Down
18 changes: 9 additions & 9 deletions unilabos/test/experiments/prcxi_9320_with_res_test.json
Original file line number Diff line number Diff line change
Expand Up @@ -812,7 +812,7 @@
"z": 0
},
"config": {
"type": "PRCXI9300Container",
"type": "PRCXI9300TipRack",
"size_x": 127,
"size_y": 85.5,
"size_z": 10,
Expand Down Expand Up @@ -6515,7 +6515,7 @@
"z": 0
},
"config": {
"type": "PRCXI9300Container",
"type": "PRCXI9300Plate",
"size_x": 127,
"size_y": 85.5,
"size_z": 10,
Expand Down Expand Up @@ -10587,7 +10587,7 @@
"z": 0
},
"config": {
"type": "PRCXI9300Container",
"type": "PRCXI9300TipRack",
"size_x": 127,
"size_y": 85.5,
"size_z": 10,
Expand Down Expand Up @@ -16291,7 +16291,7 @@
"z": 0
},
"config": {
"type": "PRCXI9300Container",
"type": "PRCXI9300Plate",
"size_x": 127,
"size_y": 85.5,
"size_z": 10,
Expand Down Expand Up @@ -20365,7 +20365,7 @@
"z": 0
},
"config": {
"type": "PRCXI9300Container",
"type": "PRCXI9300Plate",
"size_x": 127,
"size_y": 85.5,
"size_z": 10,
Expand Down Expand Up @@ -24438,7 +24438,7 @@
"z": 0
},
"config": {
"type": "PRCXI9300Container",
"type": "PRCXI9300Plate",
"size_x": 127,
"size_y": 85.5,
"size_z": 10,
Expand Down Expand Up @@ -28512,7 +28512,7 @@
"z": 0
},
"config": {
"type": "PRCXI9300Container",
"type": "PRCXI9300Plate",
"size_x": 127,
"size_y": 85.5,
"size_z": 10,
Expand Down Expand Up @@ -32585,7 +32585,7 @@
"z": 0
},
"config": {
"type": "PRCXI9300Container",
"type": "PRCXI9300Plate",
"size_x": 127,
"size_y": 85.5,
"size_z": 10,
Expand Down Expand Up @@ -36658,7 +36658,7 @@
"z": 0
},
"config": {
"type": "PRCXI9300Container",
"type": "PRCXI9300Plate",
"size_x": 127,
"size_y": 85.5,
"size_z": 10,
Expand Down