mirror of
https://github.com/deepmodeling/Uni-Lab-OS
synced 2026-04-26 18:45:25 +00:00
Merge branch 'dev' into prcix9320
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import json
|
||||
import os
|
||||
|
||||
# from nt import device_encoding
|
||||
import threading
|
||||
@@ -62,7 +63,7 @@ def main(
|
||||
rclpy.init(args=rclpy_init_args)
|
||||
else:
|
||||
logger.info("[ROS] rclpy already initialized, reusing context")
|
||||
executor = rclpy.__executor = MultiThreadedExecutor()
|
||||
executor = rclpy.__executor = MultiThreadedExecutor(num_threads=max(os.cpu_count() * 4, 48))
|
||||
# 创建主机节点
|
||||
host_node = HostNode(
|
||||
"host_node",
|
||||
@@ -124,7 +125,7 @@ def slave(
|
||||
rclpy.init(args=rclpy_init_args)
|
||||
executor = rclpy.__executor
|
||||
if not executor:
|
||||
executor = rclpy.__executor = MultiThreadedExecutor()
|
||||
executor = rclpy.__executor = MultiThreadedExecutor(num_threads=max(os.cpu_count() * 4, 48))
|
||||
|
||||
# 1.5 启动 executor 线程
|
||||
thread = threading.Thread(target=executor.spin, daemon=True, name="slave_executor_thread")
|
||||
|
||||
@@ -486,18 +486,12 @@ class BaseROS2DeviceNode(Node, Generic[T]):
|
||||
if len(rts.root_nodes) == 1 and parent_resource is not None:
|
||||
plr_instance = plr_instances[0]
|
||||
if isinstance(plr_instance, Plate):
|
||||
empty_liquid_info_in: List[Tuple[Optional[str], float]] = [(None, 0)] * plr_instance.num_items
|
||||
if len(ADD_LIQUID_TYPE) == 1 and len(LIQUID_VOLUME) == 1 and len(LIQUID_INPUT_SLOT) > 1:
|
||||
ADD_LIQUID_TYPE = ADD_LIQUID_TYPE * len(LIQUID_INPUT_SLOT)
|
||||
LIQUID_VOLUME = LIQUID_VOLUME * len(LIQUID_INPUT_SLOT)
|
||||
self.lab_logger().warning(
|
||||
f"增加液体资源时,数量为1,自动补全为 {len(LIQUID_INPUT_SLOT)} 个"
|
||||
)
|
||||
for liquid_type, liquid_volume, liquid_input_slot in zip(
|
||||
ADD_LIQUID_TYPE, LIQUID_VOLUME, LIQUID_INPUT_SLOT
|
||||
):
|
||||
empty_liquid_info_in[liquid_input_slot] = (liquid_type, liquid_volume)
|
||||
plr_instance.set_well_liquids(empty_liquid_info_in)
|
||||
try:
|
||||
# noinspection PyProtectedMember
|
||||
keys = list(plr_instance._ordering.keys())
|
||||
@@ -511,6 +505,10 @@ class BaseROS2DeviceNode(Node, Generic[T]):
|
||||
input_wells = []
|
||||
for r in LIQUID_INPUT_SLOT:
|
||||
input_wells.append(plr_instance.children[r])
|
||||
for input_well, liquid_type, liquid_volume, liquid_input_slot in zip(
|
||||
input_wells, ADD_LIQUID_TYPE, LIQUID_VOLUME, LIQUID_INPUT_SLOT
|
||||
):
|
||||
input_well.set_liquids([(liquid_type, liquid_volume, "uL")])
|
||||
final_response["liquid_input_resource_tree"] = ResourceTreeSet.from_plr_resources(
|
||||
input_wells
|
||||
).dump()
|
||||
@@ -1256,9 +1254,8 @@ class BaseROS2DeviceNode(Node, Generic[T]):
|
||||
return self._lab_logger
|
||||
|
||||
def create_ros_publisher(self, attr_name, msg_type, initial_period=5.0):
|
||||
"""创建ROS发布者,仅当方法/属性有 @topic_config 装饰器时才创建。"""
|
||||
# 检测 @topic_config 装饰器配置
|
||||
topic_config = {}
|
||||
"""创建ROS发布者。已在 status_types 中声明的属性直接创建;@topic_config 用于覆盖默认参数。"""
|
||||
topic_cfg = {}
|
||||
driver_class = type(self.driver_instance)
|
||||
|
||||
# 区分 @property 和普通方法两种情况
|
||||
@@ -1267,23 +1264,17 @@ class BaseROS2DeviceNode(Node, Generic[T]):
|
||||
)
|
||||
|
||||
if is_prop:
|
||||
# @property: 检测 fget 上的 @topic_config
|
||||
class_attr = getattr(driver_class, attr_name)
|
||||
if class_attr.fget is not None:
|
||||
topic_config = get_topic_config(class_attr.fget)
|
||||
topic_cfg = get_topic_config(class_attr.fget)
|
||||
else:
|
||||
# 普通方法: 直接检测 attr_name 方法上的 @topic_config
|
||||
if hasattr(self.driver_instance, attr_name):
|
||||
method = getattr(self.driver_instance, attr_name)
|
||||
if callable(method):
|
||||
topic_config = get_topic_config(method)
|
||||
|
||||
# 没有 @topic_config 装饰器则跳过发布
|
||||
if not topic_config:
|
||||
return
|
||||
topic_cfg = get_topic_config(method)
|
||||
|
||||
# 发布名称优先级: @topic_config(name=...) > get_ 前缀去除 > attr_name
|
||||
cfg_name = topic_config.get("name")
|
||||
cfg_name = topic_cfg.get("name")
|
||||
if cfg_name:
|
||||
publish_name = cfg_name
|
||||
elif attr_name.startswith("get_"):
|
||||
@@ -1291,10 +1282,10 @@ class BaseROS2DeviceNode(Node, Generic[T]):
|
||||
else:
|
||||
publish_name = attr_name
|
||||
|
||||
# 使用装饰器配置或默认值
|
||||
cfg_period = topic_config.get("period")
|
||||
cfg_print = topic_config.get("print_publish")
|
||||
cfg_qos = topic_config.get("qos")
|
||||
# @topic_config 参数覆盖默认值
|
||||
cfg_period = topic_cfg.get("period")
|
||||
cfg_print = topic_cfg.get("print_publish")
|
||||
cfg_qos = topic_cfg.get("qos")
|
||||
period: float = cfg_period if cfg_period is not None else initial_period
|
||||
print_publish: bool = cfg_print if cfg_print is not None else self._print_publish
|
||||
qos: int = cfg_qos if cfg_qos is not None else 10
|
||||
@@ -1486,13 +1477,9 @@ class BaseROS2DeviceNode(Node, Generic[T]):
|
||||
if uuid_indices:
|
||||
uuids = [item[1] for item in uuid_indices]
|
||||
resource_tree = await self.get_resource(uuids)
|
||||
plr_resources = resource_tree.to_plr_resources(requested_uuids=uuids)
|
||||
plr_resources = resource_tree.to_plr_resources()
|
||||
for i, (idx, _, resource_data) in enumerate(uuid_indices):
|
||||
try:
|
||||
plr_resource = plr_resources[i]
|
||||
except Exception as e:
|
||||
self.lab_logger().error(f"资源查询结果: 共 {len(queried_resources)} 个资源,但查询结果只有 {len(plr_resources)} 个资源,索引为 {i} 的资源不存在")
|
||||
raise e
|
||||
plr_resource = plr_resources[i]
|
||||
if "sample_id" in resource_data:
|
||||
plr_resource.unilabos_extra[EXTRA_SAMPLE_UUID] = resource_data["sample_id"]
|
||||
queried_resources[idx] = plr_resource
|
||||
@@ -1739,19 +1726,13 @@ class BaseROS2DeviceNode(Node, Generic[T]):
|
||||
if arg_type == "unilabos.registry.placeholder_type:ResourceSlot":
|
||||
resource_data = function_args[arg_name]
|
||||
if isinstance(resource_data, dict) and "id" in resource_data:
|
||||
uid = resource_data.get("uuid", "")
|
||||
# 优先从本地追踪器直接取(避免服务端未同步导致的空返回)
|
||||
local_fast = self.resource_tracker.uuid_to_resources.get(uid) if uid else None
|
||||
if local_fast is not None:
|
||||
function_args[arg_name] = local_fast
|
||||
else:
|
||||
try:
|
||||
function_args[arg_name] = self._convert_resources_sync(uid)[0]
|
||||
except Exception as e:
|
||||
self.lab_logger().error(
|
||||
f"转换ResourceSlot参数 {arg_name} 失败: {e}\n{traceback.format_exc()}"
|
||||
)
|
||||
raise JsonCommandInitError(f"ResourceSlot参数转换失败: {arg_name}")
|
||||
try:
|
||||
function_args[arg_name] = self._convert_resources_sync(resource_data["uuid"])[0]
|
||||
except Exception as e:
|
||||
self.lab_logger().error(
|
||||
f"转换ResourceSlot参数 {arg_name} 失败: {e}\n{traceback.format_exc()}"
|
||||
)
|
||||
raise JsonCommandInitError(f"ResourceSlot参数转换失败: {arg_name}")
|
||||
|
||||
# 处理 ResourceSlot 列表
|
||||
elif isinstance(arg_type, tuple) and len(arg_type) == 2:
|
||||
@@ -1759,23 +1740,14 @@ class BaseROS2DeviceNode(Node, Generic[T]):
|
||||
if arg_type[0] == "list" and arg_type[1] == resource_slot_type:
|
||||
resource_list = function_args[arg_name]
|
||||
if isinstance(resource_list, list):
|
||||
uuids = [r["uuid"] for r in resource_list if isinstance(r, dict) and "id" in r]
|
||||
# 先尝试本地追踪器批量取
|
||||
local_hits = [
|
||||
self.resource_tracker.uuid_to_resources[u]
|
||||
for u in uuids
|
||||
if u in self.resource_tracker.uuid_to_resources
|
||||
]
|
||||
if len(local_hits) == len(uuids):
|
||||
function_args[arg_name] = local_hits
|
||||
else:
|
||||
try:
|
||||
function_args[arg_name] = self._convert_resources_sync(*uuids) if uuids else []
|
||||
except Exception as e:
|
||||
self.lab_logger().error(
|
||||
f"转换ResourceSlot列表参数 {arg_name} 失败: {e}\n{traceback.format_exc()}"
|
||||
)
|
||||
raise JsonCommandInitError(f"ResourceSlot列表参数转换失败: {arg_name}")
|
||||
try:
|
||||
uuids = [r["uuid"] for r in resource_list if isinstance(r, dict) and "id" in r]
|
||||
function_args[arg_name] = self._convert_resources_sync(*uuids) if uuids else []
|
||||
except Exception as e:
|
||||
self.lab_logger().error(
|
||||
f"转换ResourceSlot列表参数 {arg_name} 失败: {e}\n{traceback.format_exc()}"
|
||||
)
|
||||
raise JsonCommandInitError(f"ResourceSlot列表参数转换失败: {arg_name}")
|
||||
|
||||
# todo: 默认反报送
|
||||
return function(**function_args)
|
||||
@@ -1827,18 +1799,6 @@ class BaseROS2DeviceNode(Node, Generic[T]):
|
||||
# 转换为 PLR 资源
|
||||
tree_set = ResourceTreeSet.from_raw_dict_list(raw_data)
|
||||
if not len(tree_set.trees):
|
||||
# 服务端未找到时,尝试从本地追踪器兜底(create_resource 刚完成但服务端未及时同步)
|
||||
local_hits = [
|
||||
self.resource_tracker.uuid_to_resources[uid]
|
||||
for uid in uuids_list
|
||||
if uid in self.resource_tracker.uuid_to_resources
|
||||
]
|
||||
if local_hits:
|
||||
self.lab_logger().warning(
|
||||
f"资源查询服务端返回空树,已从本地追踪器找到 "
|
||||
f"{len(local_hits)}/{len(uuids_list)} 个资源: {uuids_list}"
|
||||
)
|
||||
return local_hits
|
||||
raise Exception(f"资源查询返回空树: {raw_data}")
|
||||
plr_resources = tree_set.to_plr_resources()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user