mirror of
https://github.com/dptech-corp/Uni-Lab-OS.git
synced 2026-02-04 05:15:10 +00:00
Compare commits
2 Commits
799813f85b
...
cfe64b023b
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cfe64b023b | ||
|
|
ad1312cf26 |
@@ -4,6 +4,7 @@ HTTP客户端模块
|
||||
提供与远程服务器通信的客户端功能,只有host需要用
|
||||
"""
|
||||
|
||||
from copy import deepcopy
|
||||
import json
|
||||
import os
|
||||
import time
|
||||
@@ -75,6 +76,27 @@ class HTTPClient:
|
||||
Returns:
|
||||
Dict[str, str]: 旧UUID到新UUID的映射关系 {old_uuid: new_uuid}
|
||||
"""
|
||||
# 遍历 resources 及其所有子节点,将 pose.position.y 全部变为 -y
|
||||
def invert_y_position(resource_instance, size_y: float = 0):
|
||||
# 处理当前节点
|
||||
pose = getattr(resource_instance.res_content, "pose", None)
|
||||
if pose and hasattr(pose, "position"):
|
||||
position = getattr(pose, "position", None)
|
||||
pose_size = getattr(pose, "size", None)
|
||||
if position and hasattr(position, "y") and pose_size and hasattr(pose_size, "height"):
|
||||
position.y = size_y - position.y - pose_size.height
|
||||
# 递归处理子节点
|
||||
for child in getattr(resource_instance, "children", []):
|
||||
_size_y = 0
|
||||
if pose and hasattr(pose, "size"):
|
||||
_size_y = pose.size.height
|
||||
invert_y_position(child, _size_y)
|
||||
|
||||
# 处理所有树的所有节点,从树的根节点递归
|
||||
resources_reversed = deepcopy(resources)
|
||||
for tree in getattr(resources_reversed, "trees", []):
|
||||
root_node = getattr(tree, "root_node", tree)
|
||||
invert_y_position(root_node, root_node.res_content.pose.size.height if root_node.res_content.pose.size else 0)
|
||||
with open(os.path.join(BasicConfig.working_dir, "req_resource_tree_add.json"), "w", encoding="utf-8") as f:
|
||||
payload = {"nodes": [x for xs in resources.dump() for x in xs], "mount_uuid": mount_uuid}
|
||||
f.write(json.dumps(payload, indent=4))
|
||||
@@ -85,14 +107,14 @@ class HTTPClient:
|
||||
info(f"首次添加资源,当前远程地址: {self.remote_addr}")
|
||||
response = requests.post(
|
||||
f"{self.remote_addr}/edge/material",
|
||||
json={"nodes": [x for xs in resources.dump() for x in xs], "mount_uuid": mount_uuid},
|
||||
json={"nodes": [x for xs in resources_reversed.dump() for x in xs], "mount_uuid": mount_uuid},
|
||||
headers={"Authorization": f"Lab {self.auth}"},
|
||||
timeout=60,
|
||||
)
|
||||
else:
|
||||
response = requests.put(
|
||||
f"{self.remote_addr}/edge/material",
|
||||
json={"nodes": [x for xs in resources.dump() for x in xs], "mount_uuid": mount_uuid},
|
||||
json={"nodes": [x for xs in resources_reversed.dump() for x in xs], "mount_uuid": mount_uuid},
|
||||
headers={"Authorization": f"Lab {self.auth}"},
|
||||
timeout=10,
|
||||
)
|
||||
|
||||
@@ -30,10 +30,11 @@ from pylabrobot.liquid_handling.standard import (
|
||||
ResourceMove,
|
||||
ResourceDrop,
|
||||
)
|
||||
from pylabrobot.resources import ResourceHolder, ResourceStack, Tip, Deck, Plate, Well, TipRack, Resource, Container, Coordinate, TipSpot, Trash, PlateAdapter, TubeRack
|
||||
from pylabrobot.resources import ResourceHolder, ResourceStack, Tip, Deck, Plate, Well, TipRack, Resource, Container, Coordinate, TipSpot, Trash, PlateAdapter, TubeRack, create_homogeneous_resources, create_ordered_items_2d
|
||||
|
||||
from unilabos.devices.liquid_handling.liquid_handler_abstract import LiquidHandlerAbstract, SimpleReturn
|
||||
from unilabos.ros.nodes.base_device_node import BaseROS2DeviceNode
|
||||
from unilabos.resources.itemized_carrier import ItemizedCarrier
|
||||
from unilabos.ros.nodes.base_device_node import BaseROS2DeviceNode, ROS2DeviceNode
|
||||
|
||||
|
||||
class PRCXIError(RuntimeError):
|
||||
@@ -86,19 +87,81 @@ class PRCXI9300Container(Plate):
|
||||
category: str,
|
||||
ordering: collections.OrderedDict,
|
||||
model: Optional[str] = None,
|
||||
material_info: Optional[Dict[str, Any]] = None,
|
||||
ordering_layout: str = "col-major",
|
||||
**kwargs,
|
||||
):
|
||||
super().__init__(name, size_x, size_y, size_z, category=category, ordering=ordering, model=model)
|
||||
self._unilabos_state = {}
|
||||
self.sites = kwargs.get("sites", [])
|
||||
self.sites = create_homogeneous_resources(
|
||||
klass=ResourceHolder,
|
||||
locations=[Coordinate(0, 0, 0)],
|
||||
resource_size_x=size_x,
|
||||
resource_size_y=size_y,
|
||||
resource_size_z=size_z,
|
||||
name_prefix=name,
|
||||
)[0]
|
||||
# 为 ItemizedCarrier 添加 _unilabos_state 属性,以便与其他 PRCXI 组件兼容
|
||||
sites_resource = ItemizedCarrier(
|
||||
name=name+"_sites",
|
||||
sites={name: self.sites},
|
||||
size_x=size_x,
|
||||
size_y=size_y,
|
||||
size_z=size_z,
|
||||
category="warehouse",
|
||||
model=model,
|
||||
)
|
||||
sites_resource._unilabos_state = {} # 添加 _unilabos_state 属性
|
||||
if material_info:
|
||||
sites_resource._unilabos_state["Material"] = material_info
|
||||
|
||||
self.assign_child_resource(sites_resource, location=self.sites.location)
|
||||
|
||||
# 保存排序方式,供graphio.py的坐标映射使用
|
||||
# 使用独立属性避免与父类的layout冲突
|
||||
self.ordering_layout = ordering_layout
|
||||
|
||||
def serialize(self) -> dict:
|
||||
"""序列化时保存 ordering_layout 属性"""
|
||||
data = super().serialize()
|
||||
data['ordering_layout'] = self.ordering_layout
|
||||
return data
|
||||
|
||||
def load_state(self, state: Dict[str, Any]) -> None:
|
||||
"""从给定的状态加载工作台信息。"""
|
||||
super().load_state(state)
|
||||
self._unilabos_state = state
|
||||
|
||||
def get_site(self) -> ResourceHolder:
|
||||
"""获取容器的站点"""
|
||||
return self.sites
|
||||
|
||||
def add_resource_to_site(self, resource) -> None:
|
||||
"""向站点添加资源"""
|
||||
self.sites.assign_child_resource(resource)
|
||||
|
||||
def get_resource_at_site(self):
|
||||
"""获取站点上的资源"""
|
||||
return self.sites.children[0] if self.sites.children else None
|
||||
|
||||
def serialize_state(self) -> Dict[str, Dict[str, Any]]:
|
||||
data = super().serialize_state()
|
||||
data.update(self._unilabos_state)
|
||||
|
||||
# 避免序列化 ResourceHolder 对象
|
||||
if hasattr(self, 'sites') and self.sites:
|
||||
# 创建 sites 的可序列化版本
|
||||
if hasattr(self.sites, '__class__') and 'pylabrobot' in str(self.sites.__class__.__module__):
|
||||
data['sites'] = {
|
||||
"__pylabrobot_object__": True,
|
||||
"class": self.sites.__class__.__name__,
|
||||
"module": self.sites.__class__.__module__,
|
||||
"name": getattr(self.sites, 'name', str(self.sites))
|
||||
}
|
||||
else:
|
||||
data['sites'] = self.sites
|
||||
|
||||
return data
|
||||
class PRCXI9300Plate(Plate):
|
||||
"""
|
||||
@@ -210,9 +273,16 @@ class PRCXI9300TipRack(TipRack):
|
||||
# 使用 ordering 参数,只包含位置信息(键)
|
||||
ordering_param = collections.OrderedDict((k, None) for k in ordering.keys())
|
||||
else:
|
||||
# ordering 的值已经是对象,可以直接使用
|
||||
items = ordering
|
||||
ordering_param = None
|
||||
# ordering 的值已经是对象,需要过滤掉 None 值
|
||||
# 只保留有效的对象,用于 ordered_items 参数
|
||||
valid_items = {k: v for k, v in ordering.items() if v is not None}
|
||||
if valid_items:
|
||||
items = valid_items
|
||||
ordering_param = None
|
||||
else:
|
||||
# 如果没有有效对象,使用 ordering 参数
|
||||
items = None
|
||||
ordering_param = collections.OrderedDict((k, None) for k in ordering.keys())
|
||||
else:
|
||||
items = None
|
||||
ordering_param = None
|
||||
@@ -348,9 +418,16 @@ class PRCXI9300TubeRack(TubeRack):
|
||||
# 使用 ordering 参数,只包含位置信息(键)
|
||||
ordering_param = collections.OrderedDict((k, None) for k in ordering.keys())
|
||||
else:
|
||||
# ordering 的值已经是对象,可以直接使用
|
||||
items_to_pass = ordering
|
||||
ordering_param = None
|
||||
# ordering 的值已经是对象,需要过滤掉 None 值
|
||||
# 只保留有效的对象,用于 ordered_items 参数
|
||||
valid_items = {k: v for k, v in ordering.items() if v is not None}
|
||||
if valid_items:
|
||||
items_to_pass = valid_items
|
||||
ordering_param = None
|
||||
else:
|
||||
# 如果没有有效对象,使用 ordering 参数
|
||||
items_to_pass = None
|
||||
ordering_param = collections.OrderedDict((k, None) for k in ordering.keys())
|
||||
elif items is not None:
|
||||
# 兼容旧的 items 参数
|
||||
items_to_pass = items
|
||||
@@ -804,7 +881,7 @@ class PRCXI9300Handler(LiquidHandlerAbstract):
|
||||
**backend_kwargs,
|
||||
):
|
||||
|
||||
return await super().move_plate(
|
||||
res = await super().move_plate(
|
||||
plate,
|
||||
to,
|
||||
intermediate_locations,
|
||||
@@ -816,6 +893,12 @@ class PRCXI9300Handler(LiquidHandlerAbstract):
|
||||
target_plate_number = to,
|
||||
**backend_kwargs,
|
||||
)
|
||||
plate.unassign()
|
||||
to.assign_child_resource(plate, location=Coordinate(0, 0, 0))
|
||||
ROS2DeviceNode.run_async_func(self._ros_node.update_resource, True, **{
|
||||
"resources": [self.deck]
|
||||
})
|
||||
return res
|
||||
|
||||
class PRCXI9300Backend(LiquidHandlerBackend):
|
||||
"""PRCXI 9300 的后端实现,继承自 LiquidHandlerBackend。
|
||||
|
||||
@@ -4019,7 +4019,8 @@ liquid_handler:
|
||||
mix_liquid_height: 0.0
|
||||
mix_rate: 0
|
||||
mix_stage: ''
|
||||
mix_times: 0
|
||||
mix_times:
|
||||
- 0
|
||||
mix_vol: 0
|
||||
none_keys:
|
||||
- ''
|
||||
@@ -4175,9 +4176,11 @@ liquid_handler:
|
||||
mix_stage:
|
||||
type: string
|
||||
mix_times:
|
||||
maximum: 2147483647
|
||||
minimum: -2147483648
|
||||
type: integer
|
||||
items:
|
||||
maximum: 2147483647
|
||||
minimum: -2147483648
|
||||
type: integer
|
||||
type: array
|
||||
mix_vol:
|
||||
maximum: 2147483647
|
||||
minimum: -2147483648
|
||||
@@ -5040,7 +5043,8 @@ liquid_handler.biomek:
|
||||
mix_liquid_height: 0.0
|
||||
mix_rate: 0
|
||||
mix_stage: ''
|
||||
mix_times: 0
|
||||
mix_times:
|
||||
- 0
|
||||
mix_vol: 0
|
||||
none_keys:
|
||||
- ''
|
||||
@@ -5183,9 +5187,11 @@ liquid_handler.biomek:
|
||||
mix_stage:
|
||||
type: string
|
||||
mix_times:
|
||||
maximum: 2147483647
|
||||
minimum: -2147483648
|
||||
type: integer
|
||||
items:
|
||||
maximum: 2147483647
|
||||
minimum: -2147483648
|
||||
type: integer
|
||||
type: array
|
||||
mix_vol:
|
||||
maximum: 2147483647
|
||||
minimum: -2147483648
|
||||
@@ -9665,7 +9671,8 @@ liquid_handler.prcxi:
|
||||
mix_liquid_height: 0.0
|
||||
mix_rate: 0
|
||||
mix_stage: ''
|
||||
mix_times: 0
|
||||
mix_times:
|
||||
- 0
|
||||
mix_vol: 0
|
||||
none_keys:
|
||||
- ''
|
||||
@@ -9821,9 +9828,11 @@ liquid_handler.prcxi:
|
||||
mix_stage:
|
||||
type: string
|
||||
mix_times:
|
||||
maximum: 2147483647
|
||||
minimum: -2147483648
|
||||
type: integer
|
||||
items:
|
||||
maximum: 2147483647
|
||||
minimum: -2147483648
|
||||
type: integer
|
||||
type: array
|
||||
mix_vol:
|
||||
maximum: 2147483647
|
||||
minimum: -2147483648
|
||||
|
||||
@@ -8,8 +8,8 @@
|
||||
"parent": "",
|
||||
"pose": {
|
||||
"size": {
|
||||
"width": 562,
|
||||
"height": 394,
|
||||
"width": 542,
|
||||
"height": 374,
|
||||
"depth": 0
|
||||
}
|
||||
},
|
||||
@@ -37,7 +37,7 @@
|
||||
"model": null,
|
||||
"position": {
|
||||
"x": 0,
|
||||
"y": 240,
|
||||
"y": 700,
|
||||
"z": 0
|
||||
}
|
||||
},
|
||||
@@ -50,8 +50,8 @@
|
||||
"type": "deck",
|
||||
"class": "",
|
||||
"position": {
|
||||
"x": 10,
|
||||
"y": 10,
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
@@ -83,7 +83,7 @@
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"type": "PRCXI9300Container",
|
||||
"type": "PRCXI9300PlateAdapterSite",
|
||||
"size_x": 127,
|
||||
"size_y": 85.5,
|
||||
"size_z": 10,
|
||||
@@ -96,7 +96,7 @@
|
||||
"category": "plate",
|
||||
"model": null,
|
||||
"barcode": null,
|
||||
"ordering": {},
|
||||
|
||||
"sites": [
|
||||
{
|
||||
"label": "T1",
|
||||
@@ -128,7 +128,7 @@
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"type": "PRCXI9300Container",
|
||||
"type": "PRCXI9300PlateAdapterSite",
|
||||
"size_x": 127,
|
||||
"size_y": 85.5,
|
||||
"size_z": 10,
|
||||
@@ -141,7 +141,7 @@
|
||||
"category": "plate",
|
||||
"model": null,
|
||||
"barcode": null,
|
||||
"ordering": {},
|
||||
|
||||
"sites": [
|
||||
{
|
||||
"label": "T2",
|
||||
@@ -173,7 +173,7 @@
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"type": "PRCXI9300Container",
|
||||
"type": "PRCXI9300PlateAdapterSite",
|
||||
"size_x": 127,
|
||||
"size_y": 85.5,
|
||||
"size_z": 10,
|
||||
@@ -186,7 +186,7 @@
|
||||
"category": "plate",
|
||||
"model": null,
|
||||
"barcode": null,
|
||||
"ordering": {},
|
||||
|
||||
"sites": [
|
||||
{
|
||||
"label": "T3",
|
||||
@@ -218,7 +218,7 @@
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"type": "PRCXI9300Container",
|
||||
"type": "PRCXI9300PlateAdapterSite",
|
||||
"size_x": 127,
|
||||
"size_y": 85.5,
|
||||
"size_z": 10,
|
||||
@@ -231,7 +231,7 @@
|
||||
"category": "plate",
|
||||
"model": null,
|
||||
"barcode": null,
|
||||
"ordering": {},
|
||||
|
||||
"sites": [
|
||||
{
|
||||
"label": "T4",
|
||||
@@ -263,7 +263,7 @@
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"type": "PRCXI9300Container",
|
||||
"type": "PRCXI9300PlateAdapterSite",
|
||||
"size_x": 127,
|
||||
"size_y": 85.5,
|
||||
"size_z": 10,
|
||||
@@ -276,7 +276,7 @@
|
||||
"category": "plate",
|
||||
"model": null,
|
||||
"barcode": null,
|
||||
"ordering": {},
|
||||
|
||||
"sites": [
|
||||
{
|
||||
"label": "T5",
|
||||
@@ -308,7 +308,7 @@
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"type": "PRCXI9300Container",
|
||||
"type": "PRCXI9300PlateAdapterSite",
|
||||
"size_x": 127,
|
||||
"size_y": 85.5,
|
||||
"size_z": 10,
|
||||
@@ -321,7 +321,7 @@
|
||||
"category": "plate",
|
||||
"model": null,
|
||||
"barcode": null,
|
||||
"ordering": {},
|
||||
|
||||
"sites": [
|
||||
{
|
||||
"label": "T6",
|
||||
@@ -353,7 +353,7 @@
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"type": "PRCXI9300Container",
|
||||
"type": "PRCXI9300PlateAdapterSite",
|
||||
"size_x": 127,
|
||||
"size_y": 85.5,
|
||||
"size_z": 10,
|
||||
@@ -366,7 +366,7 @@
|
||||
"category": "plate",
|
||||
"model": null,
|
||||
"barcode": null,
|
||||
"ordering": {},
|
||||
|
||||
"sites": [
|
||||
{
|
||||
"label": "T7",
|
||||
@@ -398,7 +398,7 @@
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"type": "PRCXI9300Container",
|
||||
"type": "PRCXI9300PlateAdapterSite",
|
||||
"size_x": 127,
|
||||
"size_y": 85.5,
|
||||
"size_z": 10,
|
||||
@@ -411,7 +411,7 @@
|
||||
"category": "plate",
|
||||
"model": null,
|
||||
"barcode": null,
|
||||
"ordering": {},
|
||||
|
||||
"sites": [
|
||||
{
|
||||
"label": "T8",
|
||||
@@ -443,7 +443,7 @@
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"type": "PRCXI9300Container",
|
||||
"type": "PRCXI9300PlateAdapterSite",
|
||||
"size_x": 127,
|
||||
"size_y": 85.5,
|
||||
"size_z": 10,
|
||||
@@ -456,7 +456,7 @@
|
||||
"category": "plate",
|
||||
"model": null,
|
||||
"barcode": null,
|
||||
"ordering": {},
|
||||
|
||||
"sites": [
|
||||
{
|
||||
"label": "T9",
|
||||
@@ -488,7 +488,7 @@
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"type": "PRCXI9300Container",
|
||||
"type": "PRCXI9300PlateAdapterSite",
|
||||
"size_x": 127,
|
||||
"size_y": 85.5,
|
||||
"size_z": 10,
|
||||
@@ -501,7 +501,7 @@
|
||||
"category": "plate",
|
||||
"model": null,
|
||||
"barcode": null,
|
||||
"ordering": {},
|
||||
|
||||
"sites": [
|
||||
{
|
||||
"label": "T10",
|
||||
@@ -533,7 +533,7 @@
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"type": "PRCXI9300Container",
|
||||
"type": "PRCXI9300PlateAdapterSite",
|
||||
"size_x": 127,
|
||||
"size_y": 85.5,
|
||||
"size_z": 10,
|
||||
@@ -546,7 +546,7 @@
|
||||
"category": "plate",
|
||||
"model": null,
|
||||
"barcode": null,
|
||||
"ordering": {},
|
||||
|
||||
"sites": [
|
||||
{
|
||||
"label": "T11",
|
||||
@@ -578,7 +578,7 @@
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"type": "PRCXI9300Container",
|
||||
"type": "PRCXI9300PlateAdapterSite",
|
||||
"size_x": 127,
|
||||
"size_y": 85.5,
|
||||
"size_z": 10,
|
||||
@@ -591,7 +591,7 @@
|
||||
"category": "plate",
|
||||
"model": null,
|
||||
"barcode": null,
|
||||
"ordering": {},
|
||||
|
||||
"sites": [
|
||||
{
|
||||
"label": "T12",
|
||||
@@ -623,7 +623,7 @@
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"type": "PRCXI9300Container",
|
||||
"type": "PRCXI9300PlateAdapterSite",
|
||||
"size_x": 127,
|
||||
"size_y": 85.5,
|
||||
"size_z": 10,
|
||||
@@ -636,7 +636,7 @@
|
||||
"category": "plate",
|
||||
"model": null,
|
||||
"barcode": null,
|
||||
"ordering": {},
|
||||
|
||||
"sites": [
|
||||
{
|
||||
"label": "T13",
|
||||
@@ -668,7 +668,7 @@
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"type": "PRCXI9300Container",
|
||||
"type": "PRCXI9300PlateAdapterSite",
|
||||
"size_x": 127,
|
||||
"size_y": 85.5,
|
||||
"size_z": 10,
|
||||
@@ -681,7 +681,7 @@
|
||||
"category": "plate",
|
||||
"model": null,
|
||||
"barcode": null,
|
||||
"ordering": {},
|
||||
|
||||
"sites": [
|
||||
{
|
||||
"label": "T14",
|
||||
@@ -713,7 +713,7 @@
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"type": "PRCXI9300Container",
|
||||
"type": "PRCXI9300PlateAdapterSite",
|
||||
"size_x": 127,
|
||||
"size_y": 85.5,
|
||||
"size_z": 10,
|
||||
@@ -726,7 +726,7 @@
|
||||
"category": "plate",
|
||||
"model": null,
|
||||
"barcode": null,
|
||||
"ordering": {},
|
||||
|
||||
"sites": [
|
||||
{
|
||||
"label": "T15",
|
||||
@@ -758,7 +758,7 @@
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"type": "PRCXI9300Container",
|
||||
"type": "PRCXI9300PlateAdapterSite",
|
||||
"size_x": 127,
|
||||
"size_y": 85.5,
|
||||
"size_z": 10,
|
||||
@@ -771,7 +771,7 @@
|
||||
"category": "plate",
|
||||
"model": null,
|
||||
"barcode": null,
|
||||
"ordering": {},
|
||||
|
||||
"sites": [
|
||||
{
|
||||
"label": "T16",
|
||||
|
||||
Reference in New Issue
Block a user