mirror of
https://github.com/dptech-corp/Uni-Lab-OS.git
synced 2025-12-17 21:11:12 +00:00
liquid states
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -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,
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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):
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
Reference in New Issue
Block a user