mirror of
https://github.com/dptech-corp/Uni-Lab-OS.git
synced 2025-12-17 21:11:12 +00:00
更新transfer_resource_to_another参数,支持spot入参
This commit is contained in:
@@ -45,7 +45,7 @@ def convert_argv_dashes_to_underscores(args: argparse.ArgumentParser):
|
|||||||
for i, arg in enumerate(sys.argv):
|
for i, arg in enumerate(sys.argv):
|
||||||
for option_string in option_strings:
|
for option_string in option_strings:
|
||||||
if arg.startswith(option_string):
|
if arg.startswith(option_string):
|
||||||
new_arg = arg[:2] + arg[2 : len(option_string)].replace("-", "_") + arg[len(option_string) :]
|
new_arg = arg[:2] + arg[2:len(option_string)].replace("-", "_") + arg[len(option_string):]
|
||||||
sys.argv[i] = new_arg
|
sys.argv[i] = new_arg
|
||||||
break
|
break
|
||||||
|
|
||||||
@@ -335,12 +335,11 @@ def main():
|
|||||||
resource_edge_info.pop(edge_info - ind - 1)
|
resource_edge_info.pop(edge_info - ind - 1)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# 如果从远端获取了物料信息,则与本地物料进行同步
|
tree_set = ResourceTreeSet.from_raw_list(request_startup_json["nodes"])
|
||||||
if request_startup_json and "nodes" in request_startup_json:
|
for root_node in tree_set.root_nodes: 希望和本地的resources_config进行同步,根节点device
|
||||||
print_status("开始同步远端物料到本地...", "info")
|
id能对上的且是resource的,则自动添加进来,根节点上不是device的,也包含进来
|
||||||
remote_tree_set = ResourceTreeSet.from_raw_list(request_startup_json["nodes"])
|
device_id = root_node.
|
||||||
resource_tree_set.merge_remote_resources(remote_tree_set)
|
# tree_set.all_nodes
|
||||||
print_status("远端物料同步完成", "info")
|
|
||||||
|
|
||||||
# 使用 ResourceTreeSet 代替 list
|
# 使用 ResourceTreeSet 代替 list
|
||||||
args_dict["resources_config"] = resource_tree_set
|
args_dict["resources_config"] = resource_tree_set
|
||||||
|
|||||||
@@ -155,11 +155,12 @@ class BioyondWorkstation(WorkstationBase):
|
|||||||
"resources": [self.deck]
|
"resources": [self.deck]
|
||||||
})
|
})
|
||||||
|
|
||||||
def transfer_resource_to_another(self, resource: ResourceSlot, mount_device_id: DeviceSlot, mount_resource: ResourceSlot):
|
def transfer_resource_to_another(self, resource: List[ResourceSlot], mount_resource: List[ResourceSlot], sites: List[str], mount_device_id: DeviceSlot):
|
||||||
ROS2DeviceNode.run_async_func(self._ros_node.transfer_resource_to_another, True, **{
|
ROS2DeviceNode.run_async_func(self._ros_node.transfer_resource_to_another, True, **{
|
||||||
"plr_resources": [resource],
|
"plr_resources": resource,
|
||||||
"target_device_id": mount_device_id,
|
"target_device_id": mount_device_id,
|
||||||
"target_resource_uuid": getattr(mount_resource, "unilabos_uuid", None),
|
"target_resources": mount_resource,
|
||||||
|
"sites": sites,
|
||||||
})
|
})
|
||||||
|
|
||||||
def _configure_station_type(self, station_config: Optional[Dict[str, Any]] = None) -> None:
|
def _configure_station_type(self, station_config: Optional[Dict[str, Any]] = None) -> None:
|
||||||
|
|||||||
@@ -889,6 +889,7 @@ dispensing_station.bioyond:
|
|||||||
mount_device_id: null
|
mount_device_id: null
|
||||||
mount_resource: null
|
mount_resource: null
|
||||||
resource: null
|
resource: null
|
||||||
|
sites: null
|
||||||
handles: {}
|
handles: {}
|
||||||
placeholder_keys:
|
placeholder_keys:
|
||||||
mount_device_id: unilabos_devices
|
mount_device_id: unilabos_devices
|
||||||
@@ -904,6 +905,7 @@ dispensing_station.bioyond:
|
|||||||
mount_device_id:
|
mount_device_id:
|
||||||
type: object
|
type: object
|
||||||
mount_resource:
|
mount_resource:
|
||||||
|
items:
|
||||||
properties:
|
properties:
|
||||||
category:
|
category:
|
||||||
type: string
|
type: string
|
||||||
@@ -976,7 +978,9 @@ dispensing_station.bioyond:
|
|||||||
- data
|
- data
|
||||||
title: mount_resource
|
title: mount_resource
|
||||||
type: object
|
type: object
|
||||||
|
type: array
|
||||||
resource:
|
resource:
|
||||||
|
items:
|
||||||
properties:
|
properties:
|
||||||
category:
|
category:
|
||||||
type: string
|
type: string
|
||||||
@@ -1049,10 +1053,16 @@ dispensing_station.bioyond:
|
|||||||
- data
|
- data
|
||||||
title: resource
|
title: resource
|
||||||
type: object
|
type: object
|
||||||
|
type: array
|
||||||
|
sites:
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
type: array
|
||||||
required:
|
required:
|
||||||
- resource
|
- resource
|
||||||
- mount_device_id
|
|
||||||
- mount_resource
|
- mount_resource
|
||||||
|
- sites
|
||||||
|
- mount_device_id
|
||||||
type: object
|
type: object
|
||||||
result: {}
|
result: {}
|
||||||
required:
|
required:
|
||||||
|
|||||||
@@ -883,6 +883,7 @@ reaction_station.bioyond:
|
|||||||
mount_device_id: null
|
mount_device_id: null
|
||||||
mount_resource: null
|
mount_resource: null
|
||||||
resource: null
|
resource: null
|
||||||
|
sites: null
|
||||||
handles: {}
|
handles: {}
|
||||||
placeholder_keys:
|
placeholder_keys:
|
||||||
mount_device_id: unilabos_devices
|
mount_device_id: unilabos_devices
|
||||||
@@ -898,6 +899,7 @@ reaction_station.bioyond:
|
|||||||
mount_device_id:
|
mount_device_id:
|
||||||
type: object
|
type: object
|
||||||
mount_resource:
|
mount_resource:
|
||||||
|
items:
|
||||||
properties:
|
properties:
|
||||||
category:
|
category:
|
||||||
type: string
|
type: string
|
||||||
@@ -970,7 +972,9 @@ reaction_station.bioyond:
|
|||||||
- data
|
- data
|
||||||
title: mount_resource
|
title: mount_resource
|
||||||
type: object
|
type: object
|
||||||
|
type: array
|
||||||
resource:
|
resource:
|
||||||
|
items:
|
||||||
properties:
|
properties:
|
||||||
category:
|
category:
|
||||||
type: string
|
type: string
|
||||||
@@ -1043,10 +1047,16 @@ reaction_station.bioyond:
|
|||||||
- data
|
- data
|
||||||
title: resource
|
title: resource
|
||||||
type: object
|
type: object
|
||||||
|
type: array
|
||||||
|
sites:
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
type: array
|
||||||
required:
|
required:
|
||||||
- resource
|
- resource
|
||||||
- mount_device_id
|
|
||||||
- mount_resource
|
- mount_resource
|
||||||
|
- sites
|
||||||
|
- mount_device_id
|
||||||
type: object
|
type: object
|
||||||
result: {}
|
result: {}
|
||||||
required:
|
required:
|
||||||
|
|||||||
@@ -7134,6 +7134,7 @@ workstation.bioyond:
|
|||||||
mount_device_id: null
|
mount_device_id: null
|
||||||
mount_resource: null
|
mount_resource: null
|
||||||
resource: null
|
resource: null
|
||||||
|
sites: null
|
||||||
handles: {}
|
handles: {}
|
||||||
placeholder_keys:
|
placeholder_keys:
|
||||||
mount_device_id: unilabos_devices
|
mount_device_id: unilabos_devices
|
||||||
@@ -7149,6 +7150,7 @@ workstation.bioyond:
|
|||||||
mount_device_id:
|
mount_device_id:
|
||||||
type: object
|
type: object
|
||||||
mount_resource:
|
mount_resource:
|
||||||
|
items:
|
||||||
properties:
|
properties:
|
||||||
category:
|
category:
|
||||||
type: string
|
type: string
|
||||||
@@ -7221,7 +7223,9 @@ workstation.bioyond:
|
|||||||
- data
|
- data
|
||||||
title: mount_resource
|
title: mount_resource
|
||||||
type: object
|
type: object
|
||||||
|
type: array
|
||||||
resource:
|
resource:
|
||||||
|
items:
|
||||||
properties:
|
properties:
|
||||||
category:
|
category:
|
||||||
type: string
|
type: string
|
||||||
@@ -7294,10 +7298,16 @@ workstation.bioyond:
|
|||||||
- data
|
- data
|
||||||
title: resource
|
title: resource
|
||||||
type: object
|
type: object
|
||||||
|
type: array
|
||||||
|
sites:
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
type: array
|
||||||
required:
|
required:
|
||||||
- resource
|
- resource
|
||||||
- mount_device_id
|
|
||||||
- mount_resource
|
- mount_resource
|
||||||
|
- sites
|
||||||
|
- mount_device_id
|
||||||
type: object
|
type: object
|
||||||
result: {}
|
result: {}
|
||||||
required:
|
required:
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ from typing import Any, Dict, List, Union, Tuple
|
|||||||
|
|
||||||
import msgcenterpy
|
import msgcenterpy
|
||||||
import yaml
|
import yaml
|
||||||
|
from rosidl_parser.definition import UnboundedSequence
|
||||||
|
from unilabos_msgs.action import LiquidHandlerTransfer
|
||||||
from unilabos_msgs.msg import Resource
|
from unilabos_msgs.msg import Resource
|
||||||
|
|
||||||
from unilabos.config.config import BasicConfig
|
from unilabos.config.config import BasicConfig
|
||||||
@@ -431,6 +433,11 @@ class Registry:
|
|||||||
param_required = arg_info.get("required", True)
|
param_required = arg_info.get("required", True)
|
||||||
if param_type == "unilabos.registry.placeholder_type:ResourceSlot":
|
if param_type == "unilabos.registry.placeholder_type:ResourceSlot":
|
||||||
schema["properties"][param_name] = ros_message_to_json_schema(Resource, param_name)
|
schema["properties"][param_name] = ros_message_to_json_schema(Resource, param_name)
|
||||||
|
elif param_type == ("list", "unilabos.registry.placeholder_type:ResourceSlot"):
|
||||||
|
schema["properties"][param_name] = {
|
||||||
|
"items": ros_message_to_json_schema(Resource, param_name),
|
||||||
|
"type": "array"
|
||||||
|
}
|
||||||
else:
|
else:
|
||||||
schema["properties"][param_name] = self._generate_schema_from_info(param_name, param_type, param_default)
|
schema["properties"][param_name] = self._generate_schema_from_info(param_name, param_type, param_default)
|
||||||
if param_required:
|
if param_required:
|
||||||
@@ -543,9 +550,9 @@ class Registry:
|
|||||||
"goal_default": {i["name"]: i["default"] for i in v["args"]},
|
"goal_default": {i["name"]: i["default"] for i in v["args"]},
|
||||||
"handles": [],
|
"handles": [],
|
||||||
"placeholder_keys": {
|
"placeholder_keys": {
|
||||||
i["name"]: "unilabos_resources" if i["type"] == "unilabos.registry.placeholder_type:ResourceSlot" else "unilabos_devices"
|
i["name"]: "unilabos_resources" if i["type"] == "unilabos.registry.placeholder_type:ResourceSlot" or i["type"] == ("list", "unilabos.registry.placeholder_type:ResourceSlot") else "unilabos_devices"
|
||||||
for i in v["args"]
|
for i in v["args"]
|
||||||
if i.get("type", "") in ["unilabos.registry.placeholder_type:ResourceSlot", "unilabos.registry.placeholder_type:DeviceSlot"]
|
if i.get("type", "") in ["unilabos.registry.placeholder_type:ResourceSlot", "unilabos.registry.placeholder_type:DeviceSlot", ("list", "unilabos.registry.placeholder_type:ResourceSlot"), ("list", "unilabos.registry.placeholder_type:DeviceSlot")]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
# 不生成已配置action的动作
|
# 不生成已配置action的动作
|
||||||
|
|||||||
@@ -9,7 +9,10 @@ from unilabos_msgs.msg import Resource
|
|||||||
|
|
||||||
from unilabos.resources.container import RegularContainer
|
from unilabos.resources.container import RegularContainer
|
||||||
from unilabos.ros.msgs.message_converter import convert_to_ros_msg
|
from unilabos.ros.msgs.message_converter import convert_to_ros_msg
|
||||||
from unilabos.ros.nodes.resource_tracker import ResourceTreeSet
|
from unilabos.ros.nodes.resource_tracker import (
|
||||||
|
ResourceDictInstance,
|
||||||
|
ResourceTreeSet,
|
||||||
|
)
|
||||||
from unilabos.utils.banner_print import print_status
|
from unilabos.utils.banner_print import print_status
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -51,20 +54,51 @@ def canonicalize_nodes_data(
|
|||||||
if child in id2idx:
|
if child in id2idx:
|
||||||
nodes[id2idx[child]]["parent"] = parent
|
nodes[id2idx[child]]["parent"] = parent
|
||||||
|
|
||||||
# 第三步:打印节点信息(用于调试)
|
# 第三步:使用 ResourceInstanceDictFlatten 标准化每个节点
|
||||||
|
standardized_instances = []
|
||||||
|
known_nodes: Dict[str, ResourceDictInstance] = {} # {node_id: ResourceDictInstance}
|
||||||
|
uuid_to_instance: Dict[str, ResourceDictInstance] = {} # {uuid: ResourceDictInstance}
|
||||||
|
|
||||||
for node in nodes:
|
for node in nodes:
|
||||||
try:
|
try:
|
||||||
print_status(f"DeviceId: {node['id']}, Class: {node['class']}", "info")
|
print_status(f"DeviceId: {node['id']}, Class: {node['class']}", "info")
|
||||||
|
# 使用标准化方法
|
||||||
|
resource_instance = ResourceDictInstance.get_resource_instance_from_dict(node)
|
||||||
|
known_nodes[node["id"]] = resource_instance
|
||||||
|
uuid_to_instance[resource_instance.res_content.uuid] = resource_instance
|
||||||
|
standardized_instances.append(resource_instance)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print_status(f"Failed to read node {node.get('id', 'unknown')}: {e}", "error")
|
print_status(f"Failed to standardize node {node.get('id', 'unknown')}:\n{traceback.format_exc()}", "error")
|
||||||
|
continue
|
||||||
|
|
||||||
# 第四步:使用 from_raw_list 创建 ResourceTreeSet(自动处理标准化、parent-children关系)
|
# 第四步:建立 parent 和 children 关系
|
||||||
try:
|
for node in nodes:
|
||||||
resource_tree_set = ResourceTreeSet.from_raw_list(nodes)
|
node_id = node["id"]
|
||||||
except Exception as e:
|
if node_id not in known_nodes:
|
||||||
print_status(f"Failed to create ResourceTreeSet:\n{traceback.format_exc()}", "error")
|
continue
|
||||||
raise
|
|
||||||
|
|
||||||
|
current_instance = known_nodes[node_id]
|
||||||
|
|
||||||
|
# 优先使用 parent_uuid 进行匹配,如果不存在则使用 parent
|
||||||
|
parent_uuid = node.get("parent_uuid")
|
||||||
|
parent_id = node.get("parent")
|
||||||
|
parent_instance = None
|
||||||
|
|
||||||
|
# 优先用 parent_uuid 匹配
|
||||||
|
if parent_uuid and parent_uuid in uuid_to_instance:
|
||||||
|
parent_instance = uuid_to_instance[parent_uuid]
|
||||||
|
# 否则用 parent_id 匹配
|
||||||
|
elif parent_id and parent_id in known_nodes:
|
||||||
|
parent_instance = known_nodes[parent_id]
|
||||||
|
|
||||||
|
# 设置 parent 引用
|
||||||
|
if parent_instance:
|
||||||
|
current_instance.res_content.parent = parent_instance.res_content
|
||||||
|
# 将当前节点添加到父节点的 children 列表
|
||||||
|
parent_instance.children.append(current_instance)
|
||||||
|
|
||||||
|
# 第五步:创建 ResourceTreeSet
|
||||||
|
resource_tree_set = ResourceTreeSet.from_nested_list(standardized_instances)
|
||||||
return resource_tree_set
|
return resource_tree_set
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import copy
|
import copy
|
||||||
|
import inspect
|
||||||
import io
|
import io
|
||||||
import json
|
import json
|
||||||
import threading
|
import threading
|
||||||
@@ -332,7 +333,7 @@ class BaseROS2DeviceNode(Node, Generic[T]):
|
|||||||
# 创建资源管理客户端
|
# 创建资源管理客户端
|
||||||
self._resource_clients: Dict[str, Client] = {
|
self._resource_clients: Dict[str, Client] = {
|
||||||
"resource_add": self.create_client(ResourceAdd, "/resources/add"),
|
"resource_add": self.create_client(ResourceAdd, "/resources/add"),
|
||||||
"resource_get": self.create_client(ResourceGet, "/resources/get"),
|
"resource_get": self.create_client(SerialCommand, "/resources/get"),
|
||||||
"resource_delete": self.create_client(ResourceDelete, "/resources/delete"),
|
"resource_delete": self.create_client(ResourceDelete, "/resources/delete"),
|
||||||
"resource_update": self.create_client(ResourceUpdate, "/resources/update"),
|
"resource_update": self.create_client(ResourceUpdate, "/resources/update"),
|
||||||
"resource_list": self.create_client(ResourceList, "/resources/list"),
|
"resource_list": self.create_client(ResourceList, "/resources/list"),
|
||||||
@@ -578,6 +579,7 @@ class BaseROS2DeviceNode(Node, Generic[T]):
|
|||||||
for i in data:
|
for i in data:
|
||||||
action = i.get("action") # remove, add, update
|
action = i.get("action") # remove, add, update
|
||||||
resources_uuid: List[str] = i.get("data") # 资源数据
|
resources_uuid: List[str] = i.get("data") # 资源数据
|
||||||
|
additional_add_params = i.get("additional_add_params", {}) # 额外参数
|
||||||
self.lab_logger().info(
|
self.lab_logger().info(
|
||||||
f"[Resource Tree Update] Processing {action} operation, "
|
f"[Resource Tree Update] Processing {action} operation, "
|
||||||
f"resources count: {len(resources_uuid)}"
|
f"resources count: {len(resources_uuid)}"
|
||||||
@@ -609,7 +611,13 @@ class BaseROS2DeviceNode(Node, Generic[T]):
|
|||||||
f"物料{plr_resource}请求挂载{tree.root_node.res_content.name}的父节点{parent_uuid}不存在")
|
f"物料{plr_resource}请求挂载{tree.root_node.res_content.name}的父节点{parent_uuid}不存在")
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
parent_resource.assign_child_resource(plr_resource, location=None)
|
# 特殊兼容所有plr的物料的assign方法,和create_resource append_resource后期同步
|
||||||
|
additional_params = {}
|
||||||
|
site = additional_add_params.get("site", None)
|
||||||
|
spec = inspect.signature(parent_resource.assign_child_resource)
|
||||||
|
if "spot" in spec.parameters:
|
||||||
|
additional_params["spot"] = site
|
||||||
|
parent_resource.assign_child_resource(plr_resource, location=None, **additional_params)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.lab_logger().warning(
|
self.lab_logger().warning(
|
||||||
f"物料{plr_resource}请求挂载{tree.root_node.res_content.name}的父节点{parent_resource}[{parent_uuid}]失败!\n{traceback.format_exc()}")
|
f"物料{plr_resource}请求挂载{tree.root_node.res_content.name}的父节点{parent_resource}[{parent_uuid}]失败!\n{traceback.format_exc()}")
|
||||||
@@ -666,14 +674,20 @@ class BaseROS2DeviceNode(Node, Generic[T]):
|
|||||||
|
|
||||||
return res
|
return res
|
||||||
|
|
||||||
async def transfer_resource_to_another(self, plr_resources: List["ResourcePLR"], target_device_id, target_resource_uuid: str):
|
async def transfer_resource_to_another(self, plr_resources: List["ResourcePLR"], target_device_id: str, target_resources: List["ResourcePLR"], sites: List[str]):
|
||||||
# 准备工作
|
# 准备工作
|
||||||
uids = []
|
uids = []
|
||||||
|
target_uids = []
|
||||||
for plr_resource in plr_resources:
|
for plr_resource in plr_resources:
|
||||||
uid = getattr(plr_resource, "unilabos_uuid", None)
|
uid = getattr(plr_resource, "unilabos_uuid", None)
|
||||||
if uid is None:
|
if uid is None:
|
||||||
raise ValueError(f"物料{plr_resource}没有unilabos_uuid属性,无法转运")
|
raise ValueError(f"来源物料{plr_resource}没有unilabos_uuid属性,无法转运")
|
||||||
uids.append(uid)
|
uids.append(uid)
|
||||||
|
for target_resource in target_resources:
|
||||||
|
uid = getattr(target_resource, "unilabos_uuid", None)
|
||||||
|
if uid is None:
|
||||||
|
raise ValueError(f"目标物料{target_resource}没有unilabos_uuid属性,无法转运")
|
||||||
|
target_uids.append(uid)
|
||||||
srv_address = f"/srv{target_device_id}/s2c_resource_tree"
|
srv_address = f"/srv{target_device_id}/s2c_resource_tree"
|
||||||
sclient = self.create_client(SerialCommand, srv_address)
|
sclient = self.create_client(SerialCommand, srv_address)
|
||||||
# 等待服务可用(设置超时)
|
# 等待服务可用(设置超时)
|
||||||
@@ -688,10 +702,11 @@ class BaseROS2DeviceNode(Node, Generic[T]):
|
|||||||
}], ensure_ascii=False)), SerialCommand_Response())
|
}], ensure_ascii=False)), SerialCommand_Response())
|
||||||
|
|
||||||
# 通知云端转运资源
|
# 通知云端转运资源
|
||||||
tree_set = ResourceTreeSet.from_plr_resources(plr_resources)
|
for plr_resource, target_uid, site in zip(plr_resources, target_uids, sites):
|
||||||
|
tree_set = ResourceTreeSet.from_plr_resources([plr_resource])
|
||||||
for root_node in tree_set.root_nodes:
|
for root_node in tree_set.root_nodes:
|
||||||
root_node.res_content.parent = None
|
root_node.res_content.parent = None
|
||||||
root_node.res_content.parent_uuid = target_resource_uuid
|
root_node.res_content.parent_uuid = target_uid
|
||||||
r = SerialCommand.Request()
|
r = SerialCommand.Request()
|
||||||
r.command = json.dumps({"data": {"data": tree_set.dump()}, "action": "update"}) # 和Update Resource一致
|
r.command = json.dumps({"data": {"data": tree_set.dump()}, "action": "update"}) # 和Update Resource一致
|
||||||
response: SerialCommand_Response = await self._resource_clients["c2s_update_resource_tree"].call_async(r) # type: ignore
|
response: SerialCommand_Response = await self._resource_clients["c2s_update_resource_tree"].call_async(r) # type: ignore
|
||||||
@@ -701,7 +716,8 @@ class BaseROS2DeviceNode(Node, Generic[T]):
|
|||||||
request = SerialCommand.Request()
|
request = SerialCommand.Request()
|
||||||
request.command = json.dumps([{
|
request.command = json.dumps([{
|
||||||
"action": "add",
|
"action": "add",
|
||||||
"data": tree_set.all_nodes_uuid # 只添加父节点,子节点会自动添加
|
"data": tree_set.all_nodes_uuid, # 只添加父节点,子节点会自动添加
|
||||||
|
"additional_add_params": {"site": site}
|
||||||
}], ensure_ascii=False)
|
}], ensure_ascii=False)
|
||||||
|
|
||||||
future = sclient.call_async(request)
|
future = sclient.call_async(request)
|
||||||
@@ -872,7 +888,7 @@ class BaseROS2DeviceNode(Node, Generic[T]):
|
|||||||
for k, v in goal.get_fields_and_field_types().items():
|
for k, v in goal.get_fields_and_field_types().items():
|
||||||
if v in ["unilabos_msgs/Resource", "sequence<unilabos_msgs/Resource>"]:
|
if v in ["unilabos_msgs/Resource", "sequence<unilabos_msgs/Resource>"]:
|
||||||
self.lab_logger().info(f"{action_name} 查询资源状态: Key: {k} Type: {v}")
|
self.lab_logger().info(f"{action_name} 查询资源状态: Key: {k} Type: {v}")
|
||||||
current_resources: Union[List[Resource], List[List[Resource]]] = []
|
current_resources: List[List[Dict[str, Any]]] = []
|
||||||
# TODO: resource后面需要分组
|
# TODO: resource后面需要分组
|
||||||
only_one_resource = False
|
only_one_resource = False
|
||||||
try:
|
try:
|
||||||
@@ -881,8 +897,8 @@ class BaseROS2DeviceNode(Node, Generic[T]):
|
|||||||
r = ResourceGet.Request()
|
r = ResourceGet.Request()
|
||||||
r.id = i["id"] # splash optional
|
r.id = i["id"] # splash optional
|
||||||
r.with_children = True
|
r.with_children = True
|
||||||
response = await self._resource_clients["resource_get"].call_async(r)
|
response: SerialCommand_Response = await self._resource_clients["resource_get"].call_async(r)
|
||||||
current_resources.append(response.resources)
|
current_resources.append(json.loads(response.response))
|
||||||
else:
|
else:
|
||||||
only_one_resource = True
|
only_one_resource = True
|
||||||
r = ResourceGet.Request()
|
r = ResourceGet.Request()
|
||||||
@@ -893,23 +909,21 @@ class BaseROS2DeviceNode(Node, Generic[T]):
|
|||||||
)
|
)
|
||||||
r.with_children = True
|
r.with_children = True
|
||||||
response = await self._resource_clients["resource_get"].call_async(r)
|
response = await self._resource_clients["resource_get"].call_async(r)
|
||||||
current_resources.extend(response.resources)
|
current_resources.append(json.loads(response.response))
|
||||||
except Exception:
|
except Exception:
|
||||||
logger.error(f"资源查询失败,默认使用本地资源")
|
logger.error(f"资源查询失败,默认使用本地资源")
|
||||||
# 删除对response.resources的检查,因为它总是存在
|
# 删除对response.resources的检查,因为它总是存在
|
||||||
type_hint = action_paramtypes[k]
|
type_hint = action_paramtypes[k]
|
||||||
final_type = get_type_class(type_hint)
|
final_type = get_type_class(type_hint)
|
||||||
if only_one_resource:
|
if only_one_resource:
|
||||||
resources_list: List[Dict[str, Any]] = [convert_from_ros_msg(rs) for rs in current_resources] # type: ignore
|
tree_set = ResourceTreeSet.from_raw_list(current_resources[0])
|
||||||
self.lab_logger().debug(f"资源查询结果: {len(resources_list)} 个资源")
|
self.lab_logger().debug(f"资源查询结果: {len(tree_set.all_nodes)} 个资源")
|
||||||
final_resource = convert_resources_to_type(resources_list, final_type)
|
final_resource: List[ResourcePLR] | ResourcePLR = tree_set.to_plr_resources()[0]
|
||||||
# 判断 ACTION 是否需要特殊的物料类型如 pylabrobot.resources.Resource,并做转换
|
# 判断 ACTION 是否需要特殊的物料类型如 pylabrobot.resources.Resource,并做转换
|
||||||
else:
|
else:
|
||||||
resources_list: List[List[Dict[str, Any]]] = [[convert_from_ros_msg(rs) for rs in sub_res_list] for sub_res_list in current_resources] # type: ignore
|
final_resource: List[ResourcePLR] | ResourcePLR = []
|
||||||
final_resource = [
|
for entry in current_resources:
|
||||||
convert_resources_to_type(sub_res_list, final_type)[0]
|
final_resource.append(ResourceTreeSet.from_raw_list(entry)[0]) # type: ignore
|
||||||
for sub_res_list in resources_list
|
|
||||||
]
|
|
||||||
try:
|
try:
|
||||||
action_kwargs[k] = self.resource_tracker.figure_resource(final_resource, try_mode=False)
|
action_kwargs[k] = self.resource_tracker.figure_resource(final_resource, try_mode=False)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|||||||
@@ -29,9 +29,11 @@ from unilabos.utils.type_check import serialize_result_info, get_result_info_str
|
|||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from unilabos.devices.workstation.workstation_base import WorkstationBase
|
from unilabos.devices.workstation.workstation_base import WorkstationBase
|
||||||
|
|
||||||
|
|
||||||
class ROS2WorkstationNodeTempError(Exception):
|
class ROS2WorkstationNodeTempError(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class ROS2WorkstationNode(BaseROS2DeviceNode):
|
class ROS2WorkstationNode(BaseROS2DeviceNode):
|
||||||
"""
|
"""
|
||||||
ROS2WorkstationNode代表管理ROS2环境中设备通信和动作的协议节点。
|
ROS2WorkstationNode代表管理ROS2环境中设备通信和动作的协议节点。
|
||||||
@@ -63,10 +65,7 @@ class ROS2WorkstationNode(BaseROS2DeviceNode):
|
|||||||
driver_instance=driver_instance,
|
driver_instance=driver_instance,
|
||||||
device_id=device_id,
|
device_id=device_id,
|
||||||
status_types=status_types,
|
status_types=status_types,
|
||||||
action_value_mappings={
|
action_value_mappings={**action_value_mappings, **self.protocol_action_mappings},
|
||||||
**action_value_mappings,
|
|
||||||
**self.protocol_action_mappings
|
|
||||||
},
|
|
||||||
hardware_interface=hardware_interface,
|
hardware_interface=hardware_interface,
|
||||||
print_publish=print_publish,
|
print_publish=print_publish,
|
||||||
resource_tracker=resource_tracker,
|
resource_tracker=resource_tracker,
|
||||||
@@ -89,7 +88,8 @@ class ROS2WorkstationNode(BaseROS2DeviceNode):
|
|||||||
d = self.initialize_device(device_id, device_config)
|
d = self.initialize_device(device_id, device_config)
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
self.lab_logger().error(
|
self.lab_logger().error(
|
||||||
f"[Protocol Node] Failed to initialize device {device_id}: {ex}\n{traceback.format_exc()}")
|
f"[Protocol Node] Failed to initialize device {device_id}: {ex}\n{traceback.format_exc()}"
|
||||||
|
)
|
||||||
d = None
|
d = None
|
||||||
if d is None:
|
if d is None:
|
||||||
continue
|
continue
|
||||||
@@ -111,8 +111,7 @@ class ROS2WorkstationNode(BaseROS2DeviceNode):
|
|||||||
if (
|
if (
|
||||||
hasattr(d.driver_instance, hardware_interface["name"])
|
hasattr(d.driver_instance, hardware_interface["name"])
|
||||||
and hasattr(d.driver_instance, hardware_interface["write"])
|
and hasattr(d.driver_instance, hardware_interface["write"])
|
||||||
and (
|
and (hardware_interface["read"] is None or hasattr(d.driver_instance, hardware_interface["read"]))
|
||||||
hardware_interface["read"] is None or hasattr(d.driver_instance, hardware_interface["read"]))
|
|
||||||
):
|
):
|
||||||
|
|
||||||
name = getattr(d.driver_instance, hardware_interface["name"])
|
name = getattr(d.driver_instance, hardware_interface["name"])
|
||||||
@@ -160,7 +159,8 @@ class ROS2WorkstationNode(BaseROS2DeviceNode):
|
|||||||
node.resource_tracker = self.resource_tracker # 站内应当共享资源跟踪器
|
node.resource_tracker = self.resource_tracker # 站内应当共享资源跟踪器
|
||||||
for action_name, action_mapping in node._action_value_mappings.items():
|
for action_name, action_mapping in node._action_value_mappings.items():
|
||||||
if action_name.startswith("auto-") or str(action_mapping.get("type", "")).startswith(
|
if action_name.startswith("auto-") or str(action_mapping.get("type", "")).startswith(
|
||||||
"UniLabJsonCommand"):
|
"UniLabJsonCommand"
|
||||||
|
):
|
||||||
continue
|
continue
|
||||||
action_id = f"/devices/{device_id_abs}/{action_name}"
|
action_id = f"/devices/{device_id_abs}/{action_name}"
|
||||||
if action_id not in self._action_clients:
|
if action_id not in self._action_clients:
|
||||||
@@ -245,8 +245,10 @@ class ROS2WorkstationNode(BaseROS2DeviceNode):
|
|||||||
logs.append(step)
|
logs.append(step)
|
||||||
elif isinstance(step, list):
|
elif isinstance(step, list):
|
||||||
logs.append(step)
|
logs.append(step)
|
||||||
self.lab_logger().info(f"Goal received: {protocol_kwargs}, running steps: "
|
self.lab_logger().info(
|
||||||
f"{json.dumps(logs, indent=4, ensure_ascii=False)}")
|
f"Goal received: {protocol_kwargs}, running steps: "
|
||||||
|
f"{json.dumps(logs, indent=4, ensure_ascii=False)}"
|
||||||
|
)
|
||||||
|
|
||||||
time_start = time.time()
|
time_start = time.time()
|
||||||
time_overall = 100
|
time_overall = 100
|
||||||
@@ -268,7 +270,9 @@ class ROS2WorkstationNode(BaseROS2DeviceNode):
|
|||||||
if not ret_info.get("suc", False):
|
if not ret_info.get("suc", False):
|
||||||
raise RuntimeError(f"Step {i + 1} failed.")
|
raise RuntimeError(f"Step {i + 1} failed.")
|
||||||
except ROS2WorkstationNodeTempError as ex:
|
except ROS2WorkstationNodeTempError as ex:
|
||||||
step_results.append({"step": i + 1, "action": action["action_name"], "result": ex.args[0]})
|
step_results.append(
|
||||||
|
{"step": i + 1, "action": action["action_name"], "result": ex.args[0]}
|
||||||
|
)
|
||||||
elif isinstance(action, list):
|
elif isinstance(action, list):
|
||||||
# 如果是并行动作,同时执行
|
# 如果是并行动作,同时执行
|
||||||
actions = action
|
actions = action
|
||||||
@@ -307,8 +311,12 @@ class ROS2WorkstationNode(BaseROS2DeviceNode):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
# 捕获并记录错误信息
|
# 捕获并记录错误信息
|
||||||
str_step_results = [
|
str_step_results = [
|
||||||
{k: dict(message_to_ordereddict(v)) if k == "result" and hasattr(v, "SLOT_TYPES") else v for k, v in
|
{
|
||||||
i.items()} for i in step_results]
|
k: dict(message_to_ordereddict(v)) if k == "result" and hasattr(v, "SLOT_TYPES") else v
|
||||||
|
for k, v in i.items()
|
||||||
|
}
|
||||||
|
for i in step_results
|
||||||
|
]
|
||||||
execution_error = f"{traceback.format_exc()}\n\nStep Result: {pformat(str_step_results)}"
|
execution_error = f"{traceback.format_exc()}\n\nStep Result: {pformat(str_step_results)}"
|
||||||
execution_success = False
|
execution_success = False
|
||||||
self.lab_logger().error(f"协议 {protocol_name} 执行出错: {str(e)} \n{traceback.format_exc()}")
|
self.lab_logger().error(f"协议 {protocol_name} 执行出错: {str(e)} \n{traceback.format_exc()}")
|
||||||
@@ -405,17 +413,3 @@ class ROS2WorkstationNode(BaseROS2DeviceNode):
|
|||||||
if write_method:
|
if write_method:
|
||||||
# bound_write = MethodType(_write, device.driver_instance)
|
# bound_write = MethodType(_write, device.driver_instance)
|
||||||
setattr(device.driver_instance, write_method, _write)
|
setattr(device.driver_instance, write_method, _write)
|
||||||
|
|
||||||
async def _update_resources(self, goal, protocol_kwargs):
|
|
||||||
"""更新资源状态"""
|
|
||||||
for k, v in goal.get_fields_and_field_types().items():
|
|
||||||
if v in ["unilabos_msgs/Resource", "sequence<unilabos_msgs/Resource>"]:
|
|
||||||
if protocol_kwargs[k] is not None:
|
|
||||||
try:
|
|
||||||
r = ResourceUpdate.Request()
|
|
||||||
r.resources = [
|
|
||||||
convert_to_ros_msg(Resource, rs) for rs in nested_dict_to_list(protocol_kwargs[k])
|
|
||||||
]
|
|
||||||
await self._resource_clients["resource_update"].call_async(r)
|
|
||||||
except Exception as e:
|
|
||||||
self.lab_logger().error(f"更新资源失败: {e}")
|
|
||||||
Reference in New Issue
Block a user