stir和adjustph的中的bug修不好

This commit is contained in:
KCFeng425
2025-07-18 00:28:12 +08:00
parent 6b7564b9f9
commit 79df194ca0
7 changed files with 328 additions and 121 deletions

View File

@@ -49,7 +49,7 @@ conda env update --file unilabos-[YOUR_OS].yml -n environment_name
# Currently, you need to install the `unilabos_msgs` package
# You can download the system-specific package from the Release page
conda install ros-humble-unilabos-msgs-0.9.10-xxxxx.tar.bz2
conda install ros-humble-unilabos-msgs-0.9.12-xxxxx.tar.bz2
# Install PyLabRobot and other prerequisites
git clone https://github.com/PyLabRobot/pylabrobot plr_repo

View File

@@ -181,7 +181,7 @@ def get_vessel_display_info(vessel: Union[str, dict]) -> str:
def generate_stir_protocol(
G: nx.DiGraph,
vessel: Union[str, dict], # 🔧 修改:支持vessel字典
vessel: Union[str, dict], # 支持vessel字典或字符串
time: Union[str, float, int] = "300",
stir_time: Union[str, float, int] = "0",
time_spec: str = "",
@@ -190,28 +190,35 @@ def generate_stir_protocol(
settling_time: Union[str, float] = "60",
**kwargs
) -> List[Dict[str, Any]]:
"""
生成搅拌操作的协议序列 - 支持vessel字典
"""生成搅拌操作的协议序列 - 修复vessel参数传递"""
Args:
G: 有向图,节点为设备和容器,边为流体管道
vessel: 搅拌容器字典从XDL传入或容器ID字符串
time: 搅拌时间(支持 "300", "5 min", "1 h" 等格式)
stir_time: 指定搅拌时间优先级比time低
time_spec: 时间规格(优先级最高)
event: 事件描述
stir_speed: 搅拌速度RPM
settling_time: 沉降时间
**kwargs: 其他可选参数
Returns:
List[Dict[str, Any]]: 搅拌操作的动作序列
"""
# 🔧 核心修改从vessel参数中提取vessel_id
# 🔧 核心修改正确处理vessel参数
vessel_id = extract_vessel_id(vessel)
vessel_display = get_vessel_display_info(vessel)
# 🔧 关键修复确保vessel_resource是完整的Resource对象
if isinstance(vessel, dict):
vessel_resource = vessel # 已经是完整的Resource字典
debug_print(f"✅ 使用传入的vessel Resource对象")
else:
# 如果只是字符串构建一个基本的Resource对象
vessel_resource = {
"id": vessel,
"name": "",
"category": "",
"children": [],
"config": "",
"data": "",
"parent": "",
"pose": {
"orientation": {"w": 1.0, "x": 0.0, "y": 0.0, "z": 0.0},
"position": {"x": 0.0, "y": 0.0, "z": 0.0}
},
"sample_id": "",
"type": ""
}
debug_print(f"🔧 构建了基本的vessel Resource对象: {vessel}")
debug_print("🌪️" * 20)
debug_print("🚀 开始生成搅拌协议支持vessel字典")
debug_print(f"📝 输入参数:")
@@ -293,13 +300,14 @@ def generate_stir_protocol(
"device_id": stirrer_id,
"action_name": "stir",
"action_kwargs": {
"vessel": vessel_id, # 🔧 使用 vessel_id
"time": str(time), # 保持原始格式
# 🔧 关键修复传递vessel_id字符串而不是完整的Resource对象
"vessel": vessel_id, # 传递字符串ID不是Resource对象
"time": str(time),
"event": event,
"time_spec": time_spec,
"stir_time": float(parsed_time), # 确保是数字
"stir_speed": float(stir_speed), # 确保是数字
"settling_time": float(parsed_settling_time) # 确保是数字
"stir_time": float(parsed_time),
"stir_speed": float(stir_speed),
"settling_time": float(parsed_settling_time)
}
}
action_sequence.append(stir_action)
@@ -324,36 +332,47 @@ def generate_stir_protocol(
def generate_start_stir_protocol(
G: nx.DiGraph,
vessel: Union[str, dict], # 🔧 修改支持vessel字典
vessel: Union[str, dict],
stir_speed: float = 300.0,
purpose: str = "",
**kwargs
) -> List[Dict[str, Any]]:
"""
生成开始搅拌操作的协议序列 - 支持vessel字典
"""生成开始搅拌操作的协议序列 - 修复vessel参数传递"""
Args:
G: 有向图
vessel: 搅拌容器字典或容器ID字符串
stir_speed: 搅拌速度RPM
purpose: 搅拌目的描述
**kwargs: 其他可选参数
Returns:
List[Dict[str, Any]]: 开始搅拌操作的动作序列
"""
# 🔧 核心修改从vessel参数中提取vessel_id
# 🔧 核心修改正确处理vessel参数
vessel_id = extract_vessel_id(vessel)
vessel_display = get_vessel_display_info(vessel)
debug_print("🔄 开始生成启动搅拌协议支持vessel字典")
# 🔧 关键修复确保vessel_resource是完整的Resource对象
if isinstance(vessel, dict):
vessel_resource = vessel # 已经是完整的Resource字典
debug_print(f"✅ 使用传入的vessel Resource对象")
else:
# 如果只是字符串构建一个基本的Resource对象
vessel_resource = {
"id": vessel,
"name": "",
"category": "",
"children": [],
"config": "",
"data": "",
"parent": "",
"pose": {
"orientation": {"w": 1.0, "x": 0.0, "y": 0.0, "z": 0.0},
"position": {"x": 0.0, "y": 0.0, "z": 0.0}
},
"sample_id": "",
"type": ""
}
debug_print(f"🔧 构建了基本的vessel Resource对象: {vessel}")
debug_print("🔄 开始生成启动搅拌协议修复vessel参数")
debug_print(f"🥽 vessel: {vessel_display} (ID: {vessel_id})")
debug_print(f"🌪️ speed: {stir_speed} RPM")
debug_print(f"🎯 purpose: {purpose}")
# 基础验证
if not vessel_id or vessel_id not in G.nodes(): # 🔧 使用 vessel_id
if not vessel_id or vessel_id not in G.nodes():
debug_print("❌ 容器验证失败!")
raise ValueError("vessel 参数无效")
@@ -363,14 +382,15 @@ def generate_start_stir_protocol(
stir_speed = 300.0
# 查找设备
stirrer_id = find_connected_stirrer(G, vessel_id) # 🔧 使用 vessel_id
stirrer_id = find_connected_stirrer(G, vessel_id)
# 生成动作
# 🔧 关键修复传递vessel_id字符串
action_sequence = [{
"device_id": stirrer_id,
"action_name": "start_stir",
"action_kwargs": {
"vessel": vessel_id, # 🔧 使用 vessel_id
# 🔧 关键修复传递vessel_id字符串而不是完整的Resource对象
"vessel": vessel_id, # 传递字符串ID不是Resource对象
"stir_speed": stir_speed,
"purpose": purpose or f"启动搅拌 {stir_speed} RPM"
}
@@ -381,42 +401,56 @@ def generate_start_stir_protocol(
def generate_stop_stir_protocol(
G: nx.DiGraph,
vessel: Union[str, dict], # 🔧 修改支持vessel字典
vessel: Union[str, dict],
**kwargs
) -> List[Dict[str, Any]]:
"""
生成停止搅拌操作的协议序列 - 支持vessel字典
"""生成停止搅拌操作的协议序列 - 修复vessel参数传递"""
Args:
G: 有向图
vessel: 搅拌容器字典或容器ID字符串
**kwargs: 其他可选参数
Returns:
List[Dict[str, Any]]: 停止搅拌操作的动作序列
"""
# 🔧 核心修改从vessel参数中提取vessel_id
# 🔧 核心修改正确处理vessel参数
vessel_id = extract_vessel_id(vessel)
vessel_display = get_vessel_display_info(vessel)
debug_print("🛑 开始生成停止搅拌协议支持vessel字典")
# 🔧 关键修复确保vessel_resource是完整的Resource对象
if isinstance(vessel, dict):
vessel_resource = vessel # 已经是完整的Resource字典
debug_print(f"✅ 使用传入的vessel Resource对象")
else:
# 如果只是字符串构建一个基本的Resource对象
vessel_resource = {
"id": vessel,
"name": "",
"category": "",
"children": [],
"config": "",
"data": "",
"parent": "",
"pose": {
"orientation": {"w": 1.0, "x": 0.0, "y": 0.0, "z": 0.0},
"position": {"x": 0.0, "y": 0.0, "z": 0.0}
},
"sample_id": "",
"type": ""
}
debug_print(f"🔧 构建了基本的vessel Resource对象: {vessel}")
debug_print("🛑 开始生成停止搅拌协议修复vessel参数")
debug_print(f"🥽 vessel: {vessel_display} (ID: {vessel_id})")
# 基础验证
if not vessel_id or vessel_id not in G.nodes(): # 🔧 使用 vessel_id
if not vessel_id or vessel_id not in G.nodes():
debug_print("❌ 容器验证失败!")
raise ValueError("vessel 参数无效")
# 查找设备
stirrer_id = find_connected_stirrer(G, vessel_id) # 🔧 使用 vessel_id
stirrer_id = find_connected_stirrer(G, vessel_id)
# 生成动作
# 🔧 关键修复传递vessel_id字符串
action_sequence = [{
"device_id": stirrer_id,
"action_name": "stop_stir",
"action_kwargs": {
"vessel": vessel_id # 🔧 使用 vessel_id
# 🔧 关键修复传递vessel_id字符串而不是完整的Resource对象
"vessel": vessel_id # 传递字符串ID不是Resource对象
}
}]

View File

@@ -3650,3 +3650,176 @@ virtual_vacuum_pump:
- status
type: object
version: 0.0.1
virtual_solid_dispenser:
class:
action_value_mappings:
auto-cleanup:
feedback: {}
goal: {}
goal_default: {}
handles: []
result: {}
schema:
description: cleanup的参数schema
properties:
feedback: {}
goal:
properties: {}
type: object
result: {}
required:
- goal
title: cleanup参数
type: object
type: UniLabJsonCommandAsync
auto-initialize:
feedback: {}
goal: {}
goal_default: {}
handles: []
result: {}
schema:
description: initialize的参数schema
properties:
feedback: {}
goal:
properties: {}
type: object
result: {}
required:
- goal
title: initialize参数
type: object
type: UniLabJsonCommandAsync
add_solid:
feedback:
current_status: status
progress: progress
goal:
vessel: vessel
reagent: reagent
mass: mass
mol: mol
purpose: purpose
event: event
rate_spec: rate_spec
equiv: equiv
ratio: ratio
goal_default:
vessel: ''
reagent: ''
mass: ''
mol: ''
purpose: ''
event: ''
rate_spec: ''
equiv: ''
ratio: ''
handles: []
result:
success: success
message: message
return_info: return_info
schema:
description: ''
properties:
feedback:
properties:
current_status:
type: string
progress:
type: number
type: object
goal:
properties:
vessel:
type: string
reagent:
type: string
mass:
type: string
mol:
type: string
purpose:
type: string
event:
type: string
rate_spec:
type: string
equiv:
type: string
ratio:
type: string
type: object
result:
properties:
success:
type: boolean
message:
type: string
return_info:
type: string
type: object
required:
- goal
title: AddSolid
type: object
type: Add # 🔧 使用 Add action type
module: unilabos.devices.virtual.virtual_solid_dispenser:VirtualSolidDispenser
status_types:
status: str
current_reagent: str
dispensed_amount: float
total_operations: int
type: python
description: Virtual Solid Dispenser for Add Protocol Testing - supports mass and molar additions
handles:
- data_key: solid_out
data_source: executor
data_type: resource
description: 固体试剂输出口
handler_key: SolidOut
io_type: source
label: SolidOut
side: SOUTH
- data_key: solid_in
data_source: handle
data_type: resource
description: 固体试剂输入口(连接试剂瓶)
handler_key: SolidIn
io_type: target
label: SolidIn
side: NORTH
icon: ''
init_param_schema:
config:
properties:
max_capacity:
default: 100.0
type: number
precision:
default: 0.001
type: number
config:
type: object
device_id:
type: string
required: []
type: object
data:
properties:
status:
type: string
current_reagent:
type: string
dispensed_amount:
type: number
total_operations:
type: integer
required:
- status
- current_reagent
- dispensed_amount
- total_operations
type: object
version: 0.0.1

View File

@@ -509,68 +509,68 @@ def convert_from_ros_msg_with_mapping(ros_msg: Any, value_mapping: Dict[str, str
"""
data: Dict[str, Any] = {}
# 🔧 添加调试信息
print(f"🔍 convert_from_ros_msg_with_mapping 开始")
print(f"🔍 ros_msg 类型: {type(ros_msg)}")
print(f"🔍 ros_msg 内容: {ros_msg}")
print(f"🔍 value_mapping: {value_mapping}")
print("-" * 60)
# # 🔧 添加调试信息
# print(f"🔍 convert_from_ros_msg_with_mapping 开始")
# print(f"🔍 ros_msg 类型: {type(ros_msg)}")
# print(f"🔍 ros_msg 内容: {ros_msg}")
# print(f"🔍 value_mapping: {value_mapping}")
# print("-" * 60)
for msg_name, attr_name in value_mapping.items():
print(f"🔍 处理映射: {msg_name} -> {attr_name}")
# print(f"🔍 处理映射: {msg_name} -> {attr_name}")
msg_path = msg_name.split(".")
current = ros_msg
print(f"🔍 msg_path: {msg_path}")
print(f"🔍 current 初始值: {current} (类型: {type(current)})")
# print(f"🔍 msg_path: {msg_path}")
# print(f"🔍 current 初始值: {current} (类型: {type(current)})")
try:
if not attr_name.endswith("[]"):
# 处理单值映射
print(f"🔍 处理单值映射")
# print(f"🔍 处理单值映射")
for i, name in enumerate(msg_path):
print(f"🔍 步骤 {i}: 获取属性 '{name}'{type(current)}")
# print(f"🔍 步骤 {i}: 获取属性 '{name}' 从 {type(current)}")
if hasattr(current, name):
current = getattr(current, name)
print(f"🔍 获取到: {current} (类型: {type(current)})")
# print(f"🔍 获取到: {current} (类型: {type(current)})")
else:
print(f"❌ 属性 '{name}' 不存在于 {type(current)}")
# print(f"❌ 属性 '{name}' 不存在于 {type(current)}")
break
converted_value = convert_from_ros_msg(current)
print(f"🔍 转换后的值: {converted_value} (类型: {type(converted_value)})")
# print(f"🔍 转换后的值: {converted_value} (类型: {type(converted_value)})")
data[attr_name] = converted_value
print(f"✅ 设置 data['{attr_name}'] = {converted_value}")
# print(f"✅ 设置 data['{attr_name}'] = {converted_value}")
else:
# 处理列表值映射
print(f"🔍 处理列表值映射")
# print(f"🔍 处理列表值映射")
for i, name in enumerate(msg_path):
print(f"🔍 列表步骤 {i}: 处理 '{name}'{type(current)}")
# print(f"🔍 列表步骤 {i}: 处理 '{name}' 从 {type(current)}")
if name.endswith("[]"):
base_name = name[:-2]
print(f"🔍 数组字段 base_name: '{base_name}'")
# print(f"🔍 数组字段 base_name: '{base_name}'")
if hasattr(current, base_name):
current = list(getattr(current, base_name))
print(f"🔍 获取数组: {current} (长度: {len(current)})")
# print(f"🔍 获取数组: {current} (长度: {len(current)})")
else:
print(f"❌ 数组字段 '{base_name}' 不存在")
# print(f"❌ 数组字段 '{base_name}' 不存在")
current = []
break
else:
if isinstance(current, list):
print(f"🔍 从列表中获取属性 '{name}'")
# print(f"🔍 从列表中获取属性 '{name}'")
next_level = []
for item in current:
if hasattr(item, name):
next_level.append(getattr(item, name))
current = next_level
print(f"🔍 列表处理结果: {current} (长度: {len(current)})")
# print(f"🔍 列表处理结果: {current} (长度: {len(current)})")
elif hasattr(current, name):
current = getattr(current, name)
print(f"🔍 获取到属性: {current} (类型: {type(current)})")
# print(f"🔍 获取到属性: {current} (类型: {type(current)})")
else:
print(f"❌ 属性 '{name}' 不存在")
# print(f"❌ 属性 '{name}' 不存在")
current = []
break
@@ -578,20 +578,20 @@ def convert_from_ros_msg_with_mapping(ros_msg: Any, value_mapping: Dict[str, str
if current:
converted_list = [convert_from_ros_msg(item) for item in current]
data[attr_key] = converted_list
print(f"✅ 设置 data['{attr_key}'] = {converted_list}")
# print(f"✅ 设置 data['{attr_key}'] = {converted_list}")
else:
print(f"⚠️ 列表为空,跳过 '{attr_key}'")
except (AttributeError, TypeError) as e:
print(f"❌ 映射转换错误 {msg_name} -> {attr_name}: {e}")
# print(f"❌ 映射转换错误 {msg_name} -> {attr_name}: {e}")
logger.debug(f"Mapping conversion error for {msg_name} -> {attr_name}")
continue
print(f"🔍 当前 data 状态: {data}")
print("-" * 40)
# print(f"🔍 当前 data 状态: {data}")
# print("-" * 40)
print(f"🔍 convert_from_ros_msg_with_mapping 结束")
print(f"🔍 最终 data: {data}")
print("=" * 60)
#print(f"🔍 convert_from_ros_msg_with_mapping 结束")
#print(f"🔍 最终 data: {data}")
#print("=" * 60)
return data

View File

@@ -629,7 +629,7 @@ class BaseROS2DeviceNode(Node, Generic[T]):
execution_success = False
action_return_value = None
self.lab_logger().info(f"执行动作: {action_name}")
##### self.lab_logger().info(f"执行动作: {action_name}")
goal = goal_handle.request
# 从目标消息中提取参数, 并调用对应的方法
@@ -685,14 +685,14 @@ class BaseROS2DeviceNode(Node, Generic[T]):
final_resource = [convert_resources_to_type([i], final_type)[0] for i in resources_list]
action_kwargs[k] = self.resource_tracker.figure_resource(final_resource)
self.lab_logger().info(f"准备执行: {action_kwargs}, 函数: {ACTION.__name__}")
##### self.lab_logger().info(f"准备执行: {action_kwargs}, 函数: {ACTION.__name__}")
time_start = time.time()
time_overall = 100
# 将阻塞操作放入线程池执行
if asyncio.iscoroutinefunction(ACTION):
try:
self.lab_logger().info(f"异步执行动作 {ACTION}")
##### self.lab_logger().info(f"异步执行动作 {ACTION}")
future = ROS2DeviceNode.run_async_func(ACTION, trace_error=False, **action_kwargs)
def _handle_future_exception(fut):
@@ -702,7 +702,7 @@ class BaseROS2DeviceNode(Node, Generic[T]):
execution_success = True
except Exception as e:
execution_error = traceback.format_exc()
error(f"异步任务 {ACTION.__name__} 报错了")
##### error(f"异步任务 {ACTION.__name__} 报错了")
error(traceback.format_exc())
future.add_done_callback(_handle_future_exception)
@@ -710,7 +710,7 @@ class BaseROS2DeviceNode(Node, Generic[T]):
self.lab_logger().error(f"创建异步任务失败: {traceback.format_exc()}")
raise e
else:
self.lab_logger().info(f"同步执行动作 {ACTION}")
##### self.lab_logger().info(f"同步执行动作 {ACTION}")
future = self._executor.submit(ACTION, **action_kwargs)
def _handle_future_exception(fut):
@@ -765,7 +765,7 @@ class BaseROS2DeviceNode(Node, Generic[T]):
self.lab_logger().info(f"动作 {action_name} 已取消")
return action_type.Result()
self.lab_logger().info(f"动作执行完成: {action_name}")
##### self.lab_logger().info(f"动作执行完成: {action_name}")
del future
# 向Host更新物料当前状态
@@ -801,7 +801,7 @@ class BaseROS2DeviceNode(Node, Generic[T]):
# 发布结果
goal_handle.succeed()
self.lab_logger().info(f"设置动作成功: {action_name}")
##### self.lab_logger().info(f"设置动作成功: {action_name}")
result_values = {}
for msg_name, attr_name in action_value_mapping["result"].items():
@@ -820,7 +820,7 @@ class BaseROS2DeviceNode(Node, Generic[T]):
elif attr_name == "return_info":
setattr(result_msg, attr_name, serialize_result_info(execution_error, execution_success, action_return_value))
self.lab_logger().info(f"动作 {action_name} 完成并返回结果")
##### self.lab_logger().info(f"动作 {action_name} 完成并返回结果")
return result_msg
return execute_callback

View File

@@ -562,9 +562,9 @@ class HostNode(BaseROS2DeviceNode):
for bridge in self.bridges:
if hasattr(bridge, "publish_device_status"):
bridge.publish_device_status(self.device_status, device_id, property_name)
#self.lab_logger().debug(
# f"[Host Node] Status updated: {device_id}.{property_name} = {msg.data}"
#)
self.lab_logger().debug(
f"[Host Node] Status updated: {device_id}.{property_name} = {msg.data}"
)
def send_goal(
self,

View File

@@ -183,25 +183,25 @@ class ROS2ProtocolNode(BaseROS2DeviceNode):
goal = goal_handle.request
protocol_kwargs = convert_from_ros_msg_with_mapping(goal, action_value_mapping["goal"])
# 🔧 添加调试信息
print(f"🔍 转换后的 protocol_kwargs: {protocol_kwargs}")
print(f"🔍 vessel 在转换后: {protocol_kwargs.get('vessel', 'NOT_FOUND')}")
# # 🔧 添加调试信息
# print(f"🔍 转换后的 protocol_kwargs: {protocol_kwargs}")
# print(f"🔍 vessel 在转换后: {protocol_kwargs.get('vessel', 'NOT_FOUND')}")
# 🔧 完全禁用Host查询直接使用转换后的数据
print(f"🔧 跳过Host查询直接使用转换后的数据")
# # 🔧 完全禁用Host查询直接使用转换后的数据
# print(f"🔧 跳过Host查询直接使用转换后的数据")
# 🔧 额外验证确保vessel数据完整
if 'vessel' in protocol_kwargs:
vessel_data = protocol_kwargs['vessel']
print(f"🔍 验证vessel数据: {vessel_data}")
#print(f"🔍 验证vessel数据: {vessel_data}")
# 如果vessel是空字典尝试重新构建
if not vessel_data or (isinstance(vessel_data, dict) and not vessel_data):
print(f"⚠️ vessel数据为空尝试从原始goal重新提取...")
# print(f"⚠️ vessel数据为空尝试从原始goal重新提取...")
# 直接从原始goal提取vessel
if hasattr(goal, 'vessel') and goal.vessel:
print(f"🔍 原始goal.vessel: {goal.vessel}")
# print(f"🔍 原始goal.vessel: {goal.vessel}")
# 手动转换vessel
vessel_data = {
'id': goal.vessel.id,
@@ -212,16 +212,16 @@ class ROS2ProtocolNode(BaseROS2DeviceNode):
'data': goal.vessel.data
}
protocol_kwargs['vessel'] = vessel_data
print(f"✅ 手动重建vessel数据: {vessel_data}")
# print(f"✅ 手动重建vessel数据: {vessel_data}")
else:
print(f"❌ 无法从原始goal提取vessel数据")
# print(f"❌ 无法从原始goal提取vessel数据")
# 创建一个基本的vessel
vessel_data = {'id': 'default_vessel'}
protocol_kwargs['vessel'] = vessel_data
print(f"🔧 创建默认vessel: {vessel_data}")
# print(f"🔧 创建默认vessel: {vessel_data}")
print(f"🔍 最终传递给协议的 protocol_kwargs: {protocol_kwargs}")
print(f"🔍 最终的 vessel: {protocol_kwargs.get('vessel', 'NOT_FOUND')}")
#print(f"🔍 最终传递给协议的 protocol_kwargs: {protocol_kwargs}")
#print(f"🔍 最终的 vessel: {protocol_kwargs.get('vessel', 'NOT_FOUND')}")
from unilabos.resources.graphio import physical_setup_graph
@@ -315,7 +315,7 @@ class ROS2ProtocolNode(BaseROS2DeviceNode):
serialize_result_info(execution_error, execution_success, protocol_return_value),
)
self.lab_logger().info(f"协议 {protocol_name} 完成并返回结果")
self.lab_logger().info(f"🤩🤩🤩🤩🤩🤩协议 {protocol_name} 完成并返回结果😎😎😎😎😎😎")
return result
return execute_protocol
@@ -337,7 +337,7 @@ class ROS2ProtocolNode(BaseROS2DeviceNode):
action_client = self._action_clients[action_id]
goal_msg = convert_to_ros_msg(action_client._action_type.Goal(), action_kwargs)
self.lab_logger().info(f"发送动作请求到: {action_id}")
##### self.lab_logger().info(f"发送动作请求到: {action_id}")
action_client.wait_for_server()
# 等待动作完成
@@ -349,7 +349,7 @@ class ROS2ProtocolNode(BaseROS2DeviceNode):
return None
result_future = await handle.get_result_async()
self.lab_logger().info(f"动作完成: {action_name}")
##### self.lab_logger().info(f"动作完成: {action_name}")
return result_future.result