mirror of
https://github.com/dptech-corp/Uni-Lab-OS.git
synced 2025-12-17 13:01:12 +00:00
更新transfer_resource_to_another参数,支持spot入参
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import copy
|
||||
import inspect
|
||||
import io
|
||||
import json
|
||||
import threading
|
||||
@@ -332,7 +333,7 @@ class BaseROS2DeviceNode(Node, Generic[T]):
|
||||
# 创建资源管理客户端
|
||||
self._resource_clients: Dict[str, Client] = {
|
||||
"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_update": self.create_client(ResourceUpdate, "/resources/update"),
|
||||
"resource_list": self.create_client(ResourceList, "/resources/list"),
|
||||
@@ -578,6 +579,7 @@ class BaseROS2DeviceNode(Node, Generic[T]):
|
||||
for i in data:
|
||||
action = i.get("action") # remove, add, update
|
||||
resources_uuid: List[str] = i.get("data") # 资源数据
|
||||
additional_add_params = i.get("additional_add_params", {}) # 额外参数
|
||||
self.lab_logger().info(
|
||||
f"[Resource Tree Update] Processing {action} operation, "
|
||||
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}不存在")
|
||||
else:
|
||||
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:
|
||||
self.lab_logger().warning(
|
||||
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
|
||||
|
||||
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 = []
|
||||
target_uids = []
|
||||
for plr_resource in plr_resources:
|
||||
uid = getattr(plr_resource, "unilabos_uuid", None)
|
||||
if uid is None:
|
||||
raise ValueError(f"物料{plr_resource}没有unilabos_uuid属性,无法转运")
|
||||
raise ValueError(f"来源物料{plr_resource}没有unilabos_uuid属性,无法转运")
|
||||
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"
|
||||
sclient = self.create_client(SerialCommand, srv_address)
|
||||
# 等待服务可用(设置超时)
|
||||
@@ -688,31 +702,33 @@ class BaseROS2DeviceNode(Node, Generic[T]):
|
||||
}], ensure_ascii=False)), SerialCommand_Response())
|
||||
|
||||
# 通知云端转运资源
|
||||
tree_set = ResourceTreeSet.from_plr_resources(plr_resources)
|
||||
for root_node in tree_set.root_nodes:
|
||||
root_node.res_content.parent = None
|
||||
root_node.res_content.parent_uuid = target_resource_uuid
|
||||
r = SerialCommand.Request()
|
||||
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
|
||||
self.lab_logger().info(f"资源云端转运到{target_device_id}结果: {response.response}")
|
||||
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:
|
||||
root_node.res_content.parent = None
|
||||
root_node.res_content.parent_uuid = target_uid
|
||||
r = SerialCommand.Request()
|
||||
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
|
||||
self.lab_logger().info(f"资源云端转运到{target_device_id}结果: {response.response}")
|
||||
|
||||
# 创建请求
|
||||
request = SerialCommand.Request()
|
||||
request.command = json.dumps([{
|
||||
"action": "add",
|
||||
"data": tree_set.all_nodes_uuid # 只添加父节点,子节点会自动添加
|
||||
}], ensure_ascii=False)
|
||||
# 创建请求
|
||||
request = SerialCommand.Request()
|
||||
request.command = json.dumps([{
|
||||
"action": "add",
|
||||
"data": tree_set.all_nodes_uuid, # 只添加父节点,子节点会自动添加
|
||||
"additional_add_params": {"site": site}
|
||||
}], ensure_ascii=False)
|
||||
|
||||
future = sclient.call_async(request)
|
||||
timeout = 30.0
|
||||
start_time = time.time()
|
||||
while not future.done():
|
||||
if time.time() - start_time > timeout:
|
||||
self.lab_logger().error(f"[{self.device_id} Node-Resource] Timeout waiting for response from {target_device_id}")
|
||||
return False
|
||||
time.sleep(0.05)
|
||||
self.lab_logger().info(f"资源本地增加到{target_device_id}结果: {response.response}")
|
||||
future = sclient.call_async(request)
|
||||
timeout = 30.0
|
||||
start_time = time.time()
|
||||
while not future.done():
|
||||
if time.time() - start_time > timeout:
|
||||
self.lab_logger().error(f"[{self.device_id} Node-Resource] Timeout waiting for response from {target_device_id}")
|
||||
return False
|
||||
time.sleep(0.05)
|
||||
self.lab_logger().info(f"资源本地增加到{target_device_id}结果: {response.response}")
|
||||
return None
|
||||
|
||||
def register_device(self):
|
||||
@@ -872,7 +888,7 @@ class BaseROS2DeviceNode(Node, Generic[T]):
|
||||
for k, v in goal.get_fields_and_field_types().items():
|
||||
if v in ["unilabos_msgs/Resource", "sequence<unilabos_msgs/Resource>"]:
|
||||
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后面需要分组
|
||||
only_one_resource = False
|
||||
try:
|
||||
@@ -881,8 +897,8 @@ class BaseROS2DeviceNode(Node, Generic[T]):
|
||||
r = ResourceGet.Request()
|
||||
r.id = i["id"] # splash optional
|
||||
r.with_children = True
|
||||
response = await self._resource_clients["resource_get"].call_async(r)
|
||||
current_resources.append(response.resources)
|
||||
response: SerialCommand_Response = await self._resource_clients["resource_get"].call_async(r)
|
||||
current_resources.append(json.loads(response.response))
|
||||
else:
|
||||
only_one_resource = True
|
||||
r = ResourceGet.Request()
|
||||
@@ -893,23 +909,21 @@ class BaseROS2DeviceNode(Node, Generic[T]):
|
||||
)
|
||||
r.with_children = True
|
||||
response = await self._resource_clients["resource_get"].call_async(r)
|
||||
current_resources.extend(response.resources)
|
||||
current_resources.append(json.loads(response.response))
|
||||
except Exception:
|
||||
logger.error(f"资源查询失败,默认使用本地资源")
|
||||
# 删除对response.resources的检查,因为它总是存在
|
||||
type_hint = action_paramtypes[k]
|
||||
final_type = get_type_class(type_hint)
|
||||
if only_one_resource:
|
||||
resources_list: List[Dict[str, Any]] = [convert_from_ros_msg(rs) for rs in current_resources] # type: ignore
|
||||
self.lab_logger().debug(f"资源查询结果: {len(resources_list)} 个资源")
|
||||
final_resource = convert_resources_to_type(resources_list, final_type)
|
||||
tree_set = ResourceTreeSet.from_raw_list(current_resources[0])
|
||||
self.lab_logger().debug(f"资源查询结果: {len(tree_set.all_nodes)} 个资源")
|
||||
final_resource: List[ResourcePLR] | ResourcePLR = tree_set.to_plr_resources()[0]
|
||||
# 判断 ACTION 是否需要特殊的物料类型如 pylabrobot.resources.Resource,并做转换
|
||||
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 = [
|
||||
convert_resources_to_type(sub_res_list, final_type)[0]
|
||||
for sub_res_list in resources_list
|
||||
]
|
||||
final_resource: List[ResourcePLR] | ResourcePLR = []
|
||||
for entry in current_resources:
|
||||
final_resource.append(ResourceTreeSet.from_raw_list(entry)[0]) # type: ignore
|
||||
try:
|
||||
action_kwargs[k] = self.resource_tracker.figure_resource(final_resource, try_mode=False)
|
||||
except Exception as e:
|
||||
|
||||
@@ -29,9 +29,11 @@ from unilabos.utils.type_check import serialize_result_info, get_result_info_str
|
||||
if TYPE_CHECKING:
|
||||
from unilabos.devices.workstation.workstation_base import WorkstationBase
|
||||
|
||||
|
||||
class ROS2WorkstationNodeTempError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class ROS2WorkstationNode(BaseROS2DeviceNode):
|
||||
"""
|
||||
ROS2WorkstationNode代表管理ROS2环境中设备通信和动作的协议节点。
|
||||
@@ -63,10 +65,7 @@ class ROS2WorkstationNode(BaseROS2DeviceNode):
|
||||
driver_instance=driver_instance,
|
||||
device_id=device_id,
|
||||
status_types=status_types,
|
||||
action_value_mappings={
|
||||
**action_value_mappings,
|
||||
**self.protocol_action_mappings
|
||||
},
|
||||
action_value_mappings={**action_value_mappings, **self.protocol_action_mappings},
|
||||
hardware_interface=hardware_interface,
|
||||
print_publish=print_publish,
|
||||
resource_tracker=resource_tracker,
|
||||
@@ -89,7 +88,8 @@ class ROS2WorkstationNode(BaseROS2DeviceNode):
|
||||
d = self.initialize_device(device_id, device_config)
|
||||
except Exception as ex:
|
||||
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
|
||||
if d is None:
|
||||
continue
|
||||
@@ -109,10 +109,9 @@ class ROS2WorkstationNode(BaseROS2DeviceNode):
|
||||
if d:
|
||||
hardware_interface = d.ros_node_instance._hardware_interface
|
||||
if (
|
||||
hasattr(d.driver_instance, hardware_interface["name"])
|
||||
and hasattr(d.driver_instance, hardware_interface["write"])
|
||||
and (
|
||||
hardware_interface["read"] is None or hasattr(d.driver_instance, hardware_interface["read"]))
|
||||
hasattr(d.driver_instance, hardware_interface["name"])
|
||||
and hasattr(d.driver_instance, hardware_interface["write"])
|
||||
and (hardware_interface["read"] is None or hasattr(d.driver_instance, hardware_interface["read"]))
|
||||
):
|
||||
|
||||
name = getattr(d.driver_instance, hardware_interface["name"])
|
||||
@@ -160,7 +159,8 @@ class ROS2WorkstationNode(BaseROS2DeviceNode):
|
||||
node.resource_tracker = self.resource_tracker # 站内应当共享资源跟踪器
|
||||
for action_name, action_mapping in node._action_value_mappings.items():
|
||||
if action_name.startswith("auto-") or str(action_mapping.get("type", "")).startswith(
|
||||
"UniLabJsonCommand"):
|
||||
"UniLabJsonCommand"
|
||||
):
|
||||
continue
|
||||
action_id = f"/devices/{device_id_abs}/{action_name}"
|
||||
if action_id not in self._action_clients:
|
||||
@@ -245,8 +245,10 @@ class ROS2WorkstationNode(BaseROS2DeviceNode):
|
||||
logs.append(step)
|
||||
elif isinstance(step, list):
|
||||
logs.append(step)
|
||||
self.lab_logger().info(f"Goal received: {protocol_kwargs}, running steps: "
|
||||
f"{json.dumps(logs, indent=4, ensure_ascii=False)}")
|
||||
self.lab_logger().info(
|
||||
f"Goal received: {protocol_kwargs}, running steps: "
|
||||
f"{json.dumps(logs, indent=4, ensure_ascii=False)}"
|
||||
)
|
||||
|
||||
time_start = time.time()
|
||||
time_overall = 100
|
||||
@@ -268,7 +270,9 @@ class ROS2WorkstationNode(BaseROS2DeviceNode):
|
||||
if not ret_info.get("suc", False):
|
||||
raise RuntimeError(f"Step {i + 1} failed.")
|
||||
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):
|
||||
# 如果是并行动作,同时执行
|
||||
actions = action
|
||||
@@ -307,8 +311,12 @@ class ROS2WorkstationNode(BaseROS2DeviceNode):
|
||||
except Exception as e:
|
||||
# 捕获并记录错误信息
|
||||
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_success = False
|
||||
self.lab_logger().error(f"协议 {protocol_name} 执行出错: {str(e)} \n{traceback.format_exc()}")
|
||||
@@ -381,7 +389,7 @@ class ROS2WorkstationNode(BaseROS2DeviceNode):
|
||||
"""还没有改过的部分"""
|
||||
|
||||
def _setup_hardware_proxy(
|
||||
self, device: ROS2DeviceNode, communication_device: ROS2DeviceNode, read_method, write_method
|
||||
self, device: ROS2DeviceNode, communication_device: ROS2DeviceNode, read_method, write_method
|
||||
):
|
||||
"""为设备设置硬件接口代理"""
|
||||
# extra_info = [getattr(device.driver_instance, info) for info in communication_device.ros_node_instance._hardware_interface.get("extra_info", [])]
|
||||
@@ -405,17 +413,3 @@ class ROS2WorkstationNode(BaseROS2DeviceNode):
|
||||
if write_method:
|
||||
# bound_write = MethodType(_write, device.driver_instance)
|
||||
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