liquid states

This commit is contained in:
Xuwznln
2025-06-09 00:52:07 +08:00
parent d2782943b4
commit f2e1d8a6c8
8 changed files with 17248 additions and 507 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -60,7 +60,7 @@ class HTTPClient:
Dict: 返回的资源数据 Dict: 返回的资源数据
""" """
response = requests.get( response = requests.get(
f"{self.remote_addr}/lab/resource/", f"{self.remote_addr}/lab/resource/?edge_format=1",
params={"id": id, "with_children": with_children}, params={"id": id, "with_children": with_children},
headers={"Authorization": f"lab {self.auth}"}, headers={"Authorization": f"lab {self.auth}"},
timeout=5, timeout=5,
@@ -96,7 +96,7 @@ class HTTPClient:
Response: API响应对象 Response: API响应对象
""" """
response = requests.patch( response = requests.patch(
f"{self.remote_addr}/lab/resource/batch_update/", f"{self.remote_addr}/lab/resource/batch_update/?edge_format=1",
json=resources, json=resources,
headers={"Authorization": f"lab {self.auth}"}, headers={"Authorization": f"lab {self.auth}"},
timeout=5, timeout=5,

View File

@@ -326,8 +326,6 @@ def convert_resources_to_type(
elif all(issubclass(t, ResourcePLR) for t in resource_type): elif all(issubclass(t, ResourcePLR) for t in resource_type):
resources_tree = dict_to_tree({r["id"]: r for r in resources_list}) resources_tree = dict_to_tree({r["id"]: r for r in resources_list})
return [resource_ulab_to_plr(r, plr_model) for r in resources_tree] return [resource_ulab_to_plr(r, plr_model) for r in resources_tree]
else: else:
return None return None

View File

@@ -6,7 +6,6 @@ import time
from typing import Optional, Dict, Any, List from typing import Optional, Dict, Any, List
import rclpy import rclpy
from unilabos.ros.nodes.presets.joint_republisher import JointRepublisher
from unilabos.ros.nodes.presets.resource_mesh_manager import ResourceMeshManager from unilabos.ros.nodes.presets.resource_mesh_manager import ResourceMeshManager
from unilabos.ros.nodes.resource_tracker import DeviceNodeResourceTracker from unilabos.ros.nodes.resource_tracker import DeviceNodeResourceTracker
from unilabos.devices.ros_dev.liquid_handler_joint_publisher import LiquidHandlerJointPublisher from unilabos.devices.ros_dev.liquid_handler_joint_publisher import LiquidHandlerJointPublisher
@@ -70,6 +69,8 @@ def main(
) )
if visual != "disable": if visual != "disable":
from unilabos.ros.nodes.presets.joint_republisher import JointRepublisher
resource_mesh_manager = ResourceMeshManager( resource_mesh_manager = ResourceMeshManager(
resources_mesh_config, resources_mesh_config,
resources_config, resources_config,
@@ -80,11 +81,11 @@ def main(
'joint_republisher', 'joint_republisher',
host_node.resource_tracker host_node.resource_tracker
) )
lh_joint_pub = LiquidHandlerJointPublisher(resources_config=resources_config, resource_tracker=host_node.resource_tracker) lh_joint_pub = LiquidHandlerJointPublisher(resources_config=resources_config,
executor.add_node(lh_joint_pub) resource_tracker=host_node.resource_tracker)
executor.add_node(resource_mesh_manager) executor.add_node(resource_mesh_manager)
executor.add_node(joint_republisher) executor.add_node(joint_republisher)
executor.add_node(lh_joint_pub)
thread = threading.Thread(target=executor.spin, daemon=True, name="host_executor_thread") thread = threading.Thread(target=executor.spin, daemon=True, name="host_executor_thread")
thread.start() thread.start()

View File

@@ -740,16 +740,23 @@ class BaseROS2DeviceNode(Node, Generic[T]):
self.lab_logger().info(f"更新资源状态: {k}") self.lab_logger().info(f"更新资源状态: {k}")
r = ResourceUpdate.Request() r = ResourceUpdate.Request()
# 仅当action_kwargs[k]不为None时尝试转换 # 仅当action_kwargs[k]不为None时尝试转换
akv = action_kwargs[k] akv = action_kwargs[k] # 已经是完成转换的物料了只需要转换成ros msg Resource了
apv = action_paramtypes[k] apv = action_paramtypes[k]
final_type = get_type_class(apv) final_type = get_type_class(apv)
if final_type is None: if final_type is None:
continue continue
try: try:
r.resources = [ seen = set()
convert_to_ros_msg(Resource, self.resource_tracker.root_resource(rs)) unique_resources = []
for rs in convert_resources_from_type(akv, final_type) # type: ignore # FIXME # 考虑反查到最大的 for rs in akv:
] res = self.resource_tracker.parent_resource(rs) # 获取 resource 对象
if id(res) not in seen:
seen.add(id(res))
converted_list = convert_resources_from_type([res], final_type)
unique_resources.extend([convert_to_ros_msg(Resource, converted) for converted in converted_list])
r.resources = unique_resources
response = await self._resource_clients["resource_update"].call_async(r) response = await self._resource_clients["resource_update"].call_async(r)
self.lab_logger().debug(f"资源更新结果: {response}") self.lab_logger().debug(f"资源更新结果: {response}")
except Exception as e: except Exception as e:

View File

@@ -1,3 +1,5 @@
from typing import List, Tuple, Any
from unilabos.utils.log import logger from unilabos.utils.log import logger
@@ -5,12 +7,12 @@ class DeviceNodeResourceTracker(object):
def __init__(self): def __init__(self):
self.resources = [] self.resources = []
self.root_resource2resource = {} self.resource2parent_resource = {}
pass pass
def root_resource(self, resource): def parent_resource(self, resource):
if id(resource) in self.root_resource2resource: if id(resource) in self.resource2parent_resource:
return self.root_resource2resource[id(resource)] return self.resource2parent_resource[id(resource)]
else: else:
return resource return resource
@@ -44,20 +46,21 @@ class DeviceNodeResourceTracker(object):
self.loop_find_resource(r, resource_cls_type, identifier_key, getattr(query_resource, identifier_key)) self.loop_find_resource(r, resource_cls_type, identifier_key, getattr(query_resource, identifier_key))
) )
assert len(res_list) == 1, f"{query_resource} 找到多个资源,请检查资源是否唯一: {res_list}" assert len(res_list) == 1, f"{query_resource} 找到多个资源,请检查资源是否唯一: {res_list}"
self.root_resource2resource[id(query_resource)] = res_list[0] self.resource2parent_resource[id(query_resource)] = res_list[0][0]
self.resource2parent_resource[id(res_list[0][1])] = res_list[0][0]
# 后续加入其他对比方式 # 后续加入其他对比方式
return res_list[0] return res_list[0][1]
def loop_find_resource(self, resource, target_resource_cls_type, identifier_key, compare_value): def loop_find_resource(self, resource, target_resource_cls_type, identifier_key, compare_value, parent_res=None) -> List[Tuple[Any, Any]]:
res_list = [] res_list = []
# print(resource, target_resource_cls_type, identifier_key, compare_value) # print(resource, target_resource_cls_type, identifier_key, compare_value)
children = getattr(resource, "children", []) children = getattr(resource, "children", [])
for child in children: for child in children:
res_list.extend(self.loop_find_resource(child, target_resource_cls_type, identifier_key, compare_value)) res_list.extend(self.loop_find_resource(child, target_resource_cls_type, identifier_key, compare_value, resource))
if target_resource_cls_type == type(resource) or target_resource_cls_type == dict: if target_resource_cls_type == type(resource) or target_resource_cls_type == dict:
if hasattr(resource, identifier_key): if hasattr(resource, identifier_key):
if getattr(resource, identifier_key) == compare_value: if getattr(resource, identifier_key) == compare_value:
res_list.append(resource) res_list.append((parent_res, resource))
return res_list return res_list
def filter_find_list(self, res_list, compare_std_dict): def filter_find_list(self, res_list, compare_std_dict):

View File

@@ -105,40 +105,41 @@ class PyLabRobotCreator(DeviceClassCreator[T]):
return nested_dict_to_list(resource), Resource return nested_dict_to_list(resource), Resource
return resource, source_type return resource, source_type
def _process_resource_references(self, data: Any, to_dict=False) -> Any: def _process_resource_references(self, data: Any, to_dict=False, states=None, prefix_path="") -> Any:
""" """
递归处理资源引用替换_resource_child_name对应的资源 递归处理资源引用替换_resource_child_name对应的资源
Args: Args:
data: 需要处理的数据,可能是字典、列表或其他类型 data: 需要处理的数据,可能是字典、列表或其他类型
to_dict: 转换成对应的实例,还是转换成对应的字典 to_dict: 是否返回字典形式的资源
states: 用于保存所有资源状态
prefix_path: 当前递归路径
Returns: Returns:
处理后的数据 处理后的数据
""" """
from pylabrobot.resources import Deck, Resource from pylabrobot.resources import Deck, Resource
if states is None:
states = {}
if isinstance(data, dict): if isinstance(data, dict):
# 检查是否包含资源引用
if "_resource_child_name" in data: if "_resource_child_name" in data:
child_name = data["_resource_child_name"] child_name = data["_resource_child_name"]
if child_name in self.children: if child_name in self.children:
# 找到了对应的资源
resource = self.children[child_name] resource = self.children[child_name]
# 检查是否需要转换资源类型
if "_resource_type" in data: if "_resource_type" in data:
type_path = data["_resource_type"] type_path = data["_resource_type"]
try: try:
# 尝试导入指定的类型
target_type = import_manager.get_class(type_path) target_type = import_manager.get_class(type_path)
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)
# 在截图中格式是deserialize所以这里要转成plr resource可deserialize的字典
# 这样后面执行deserialize的时候能够正确反序列化对应的物料
resource_instance: Resource = resource_ulab_to_plr(resource, contain_model) resource_instance: Resource = resource_ulab_to_plr(resource, contain_model)
# 使用 prefix_path 作为 key 存储资源状态
if to_dict: if to_dict:
return resource_instance.serialize() serialized = resource_instance.serialize()
states[prefix_path] = resource_instance.serialize_all_state()
return serialized
else: else:
self.resource_tracker.add_resource(resource_instance) self.resource_tracker.add_resource(resource_instance)
return resource_instance return resource_instance
@@ -151,18 +152,21 @@ class PyLabRobotCreator(DeviceClassCreator[T]):
else: else:
logger.warning(f"找不到资源引用 '{child_name}',保持原值不变") logger.warning(f"找不到资源引用 '{child_name}',保持原值不变")
# 递归处理字典的每个值 # 递归处理每个
result = {} result = {}
for key, value in data.items(): for key, value in data.items():
result[key] = self._process_resource_references(value, to_dict) new_prefix = f"{prefix_path}.{key}" if prefix_path else key
result[key] = self._process_resource_references(value, to_dict, states, new_prefix)
return result return result
# 处理列表类型
elif isinstance(data, list): elif isinstance(data, list):
return [self._process_resource_references(item, to_dict) for item in data] return [
self._process_resource_references(item, to_dict, states, f"{prefix_path}[{i}]")
for i, item in enumerate(data)
]
# 其他类型直接返回 else:
return data return data
def create_instance(self, data: Dict[str, Any]) -> Optional[T]: def create_instance(self, data: Dict[str, Any]) -> Optional[T]:
""" """
@@ -187,10 +191,18 @@ class PyLabRobotCreator(DeviceClassCreator[T]):
logger.debug(f"自动补充 _resource_type: {data[param_name]['_resource_type']}") logger.debug(f"自动补充 _resource_type: {data[param_name]['_resource_type']}")
# 首先处理资源引用 # 首先处理资源引用
processed_data = self._process_resource_references(data, to_dict=True) states = {}
processed_data = self._process_resource_references(data, to_dict=True, states=states)
try: try:
self.device_instance = deserialize_method(**processed_data) self.device_instance = deserialize_method(**processed_data)
all_states = self.device_instance.serialize_all_state()
for k, v in states.items():
logger.debug(f"PyLabRobot反序列化设置状态{k}")
for kk, vv in all_states.items():
if kk not in v:
v[kk] = vv
self.device_instance.deck.load_all_state(v)
self.resource_tracker.add_resource(self.device_instance) self.resource_tracker.add_resource(self.device_instance)
self.post_create() self.post_create()
return self.device_instance # type: ignore return self.device_instance # type: ignore
@@ -225,6 +237,10 @@ class PyLabRobotCreator(DeviceClassCreator[T]):
if hasattr(self.device_instance, "setup") and asyncio.iscoroutinefunction(getattr(self.device_instance, "setup")): if hasattr(self.device_instance, "setup") and asyncio.iscoroutinefunction(getattr(self.device_instance, "setup")):
from unilabos.ros.nodes.base_device_node import ROS2DeviceNode from unilabos.ros.nodes.base_device_node import ROS2DeviceNode
def done_cb(*args): def done_cb(*args):
from pylabrobot.resources import set_volume_tracking
# from pylabrobot.resources import set_tip_tracking
set_volume_tracking(enabled=True)
# set_tip_tracking(enabled=True) # 序列化tip_spot has为False
logger.debug(f"PyLabRobot设备实例 {self.device_instance} 设置完成") logger.debug(f"PyLabRobot设备实例 {self.device_instance} 设置完成")
from unilabos.config.config import BasicConfig from unilabos.config.config import BasicConfig
if BasicConfig.vis_2d_enable: if BasicConfig.vis_2d_enable: