mirror of
https://github.com/dptech-corp/Uni-Lab-OS.git
synced 2026-02-16 12:45:19 +00:00
Compare commits
3 Commits
f791c1a342
...
0063df4cf3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0063df4cf3 | ||
|
|
e570ba4976 | ||
|
|
e8c1f76dbb |
0
unilabos/devices/liquid_handling/prcxi/__init__.py
Normal file
0
unilabos/devices/liquid_handling/prcxi/__init__.py
Normal file
@@ -72,7 +72,7 @@ class VirtualFilter:
|
|||||||
"""Execute filter action - 完全按照 Filter.action 参数 🌊"""
|
"""Execute filter action - 完全按照 Filter.action 参数 🌊"""
|
||||||
vessel_id, _ = get_vessel(vessel)
|
vessel_id, _ = get_vessel(vessel)
|
||||||
filtrate_vessel_id, _ = get_vessel(filtrate_vessel) if filtrate_vessel else (f"{vessel_id}_filtrate", {})
|
filtrate_vessel_id, _ = get_vessel(filtrate_vessel) if filtrate_vessel else (f"{vessel_id}_filtrate", {})
|
||||||
|
|
||||||
# 🔧 新增:温度自动调整
|
# 🔧 新增:温度自动调整
|
||||||
original_temp = temp
|
original_temp = temp
|
||||||
if temp == 0.0:
|
if temp == 0.0:
|
||||||
@@ -216,12 +216,17 @@ class VirtualFilter:
|
|||||||
def current_temp(self) -> float:
|
def current_temp(self) -> float:
|
||||||
"""Filter.action feedback 字段 🌡️"""
|
"""Filter.action feedback 字段 🌡️"""
|
||||||
return self.data.get("current_temp", 25.0)
|
return self.data.get("current_temp", 25.0)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def current_status(self) -> str:
|
||||||
|
"""Filter.action feedback 字段 📋"""
|
||||||
|
return self.data.get("current_status", "")
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def filtered_volume(self) -> float:
|
def filtered_volume(self) -> float:
|
||||||
"""Filter.action feedback 字段 💧"""
|
"""Filter.action feedback 字段 💧"""
|
||||||
return self.data.get("filtered_volume", 0.0)
|
return self.data.get("filtered_volume", 0.0)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def message(self) -> str:
|
def message(self) -> str:
|
||||||
return self.data.get("message", "")
|
return self.data.get("message", "")
|
||||||
|
|||||||
@@ -340,14 +340,14 @@ def convert_resources_to_type(
|
|||||||
Returns:
|
Returns:
|
||||||
List of resources in the given type.
|
List of resources in the given type.
|
||||||
"""
|
"""
|
||||||
if resource_type == dict:
|
if resource_type == dict or resource_type == str:
|
||||||
return list_to_nested_dict(resources_list)
|
return list_to_nested_dict(resources_list)
|
||||||
elif isinstance(resource_type, type) and issubclass(resource_type, ResourcePLR):
|
elif isinstance(resource_type, type) and issubclass(resource_type, ResourcePLR):
|
||||||
if isinstance(resources_list, dict):
|
if isinstance(resources_list, dict):
|
||||||
return resource_ulab_to_plr(resources_list, plr_model)
|
return resource_ulab_to_plr(resources_list, plr_model)
|
||||||
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(resources_tree[0], plr_model)
|
return resource_ulab_to_plr(resources_tree[0], plr_model)
|
||||||
elif isinstance(resource_type, list) :
|
elif isinstance(resource_type, list):
|
||||||
if all((get_origin(t) is Union) for t in resource_type):
|
if all((get_origin(t) is Union) 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]
|
||||||
|
|||||||
@@ -656,6 +656,7 @@ class BaseROS2DeviceNode(Node, Generic[T]):
|
|||||||
|
|
||||||
action_kwargs = convert_from_ros_msg_with_mapping(goal, action_value_mapping["goal"])
|
action_kwargs = convert_from_ros_msg_with_mapping(goal, action_value_mapping["goal"])
|
||||||
self.lab_logger().debug(f"任务 {ACTION.__name__} 接收到原始目标: {action_kwargs}")
|
self.lab_logger().debug(f"任务 {ACTION.__name__} 接收到原始目标: {action_kwargs}")
|
||||||
|
error_skip = False
|
||||||
# 向Host查询物料当前状态,如果是host本身的增加物料的请求,则直接跳过
|
# 向Host查询物料当前状态,如果是host本身的增加物料的请求,则直接跳过
|
||||||
if action_name not in ["create_resource_detailed", "create_resource"]:
|
if action_name not in ["create_resource_detailed", "create_resource"]:
|
||||||
for k, v in goal.get_fields_and_field_types().items():
|
for k, v in goal.get_fields_and_field_types().items():
|
||||||
@@ -665,7 +666,7 @@ class BaseROS2DeviceNode(Node, Generic[T]):
|
|||||||
# TODO: resource后面需要分组
|
# TODO: resource后面需要分组
|
||||||
only_one_resource = False
|
only_one_resource = False
|
||||||
try:
|
try:
|
||||||
if len(action_kwargs[k]) > 1:
|
if isinstance(action_kwargs[k], list) and len(action_kwargs[k]) > 1:
|
||||||
for i in action_kwargs[k]:
|
for i in action_kwargs[k]:
|
||||||
r = ResourceGet.Request()
|
r = ResourceGet.Request()
|
||||||
r.id = i["id"] # splash optional
|
r.id = i["id"] # splash optional
|
||||||
@@ -695,17 +696,43 @@ class BaseROS2DeviceNode(Node, Generic[T]):
|
|||||||
final_resource = convert_resources_to_type(resources_list, final_type)
|
final_resource = convert_resources_to_type(resources_list, final_type)
|
||||||
else:
|
else:
|
||||||
final_resource = [convert_resources_to_type([i], final_type)[0] for i in resources_list]
|
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)
|
try:
|
||||||
|
action_kwargs[k] = self.resource_tracker.figure_resource(final_resource)
|
||||||
|
except Exception as e:
|
||||||
|
self.lab_logger().error(f"物料实例获取失败: {e}\n{traceback.format_exc()}")
|
||||||
|
error_skip = True
|
||||||
|
execution_error = traceback.format_exc()
|
||||||
|
break
|
||||||
|
|
||||||
##### self.lab_logger().info(f"准备执行: {action_kwargs}, 函数: {ACTION.__name__}")
|
##### self.lab_logger().info(f"准备执行: {action_kwargs}, 函数: {ACTION.__name__}")
|
||||||
time_start = time.time()
|
time_start = time.time()
|
||||||
time_overall = 100
|
time_overall = 100
|
||||||
future = None
|
future = None
|
||||||
# 将阻塞操作放入线程池执行
|
if not error_skip:
|
||||||
if asyncio.iscoroutinefunction(ACTION):
|
# 将阻塞操作放入线程池执行
|
||||||
try:
|
if asyncio.iscoroutinefunction(ACTION):
|
||||||
##### self.lab_logger().info(f"异步执行动作 {ACTION}")
|
try:
|
||||||
future = ROS2DeviceNode.run_async_func(ACTION, trace_error=False, **action_kwargs)
|
##### self.lab_logger().info(f"异步执行动作 {ACTION}")
|
||||||
|
future = ROS2DeviceNode.run_async_func(ACTION, trace_error=False, **action_kwargs)
|
||||||
|
|
||||||
|
def _handle_future_exception(fut):
|
||||||
|
nonlocal execution_error, execution_success, action_return_value
|
||||||
|
try:
|
||||||
|
action_return_value = fut.result()
|
||||||
|
execution_success = True
|
||||||
|
except Exception as e:
|
||||||
|
execution_error = traceback.format_exc()
|
||||||
|
error(f"异步任务 {ACTION.__name__} 报错了\n{traceback.format_exc()}\n原始输入:{action_kwargs}")
|
||||||
|
error(traceback.format_exc())
|
||||||
|
|
||||||
|
future.add_done_callback(_handle_future_exception)
|
||||||
|
except Exception as e:
|
||||||
|
execution_error = traceback.format_exc()
|
||||||
|
execution_success = False
|
||||||
|
self.lab_logger().error(f"创建异步任务失败: {traceback.format_exc()}")
|
||||||
|
else:
|
||||||
|
##### self.lab_logger().info(f"同步执行动作 {ACTION}")
|
||||||
|
future = self._executor.submit(ACTION, **action_kwargs)
|
||||||
|
|
||||||
def _handle_future_exception(fut):
|
def _handle_future_exception(fut):
|
||||||
nonlocal execution_error, execution_success, action_return_value
|
nonlocal execution_error, execution_success, action_return_value
|
||||||
@@ -713,28 +740,9 @@ class BaseROS2DeviceNode(Node, Generic[T]):
|
|||||||
action_return_value = fut.result()
|
action_return_value = fut.result()
|
||||||
execution_success = True
|
execution_success = True
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
execution_error = traceback.format_exc()
|
error(f"同步任务 {ACTION.__name__} 报错了\n{traceback.format_exc()}\n原始输入:{action_kwargs}")
|
||||||
error(f"异步任务 {ACTION.__name__} 报错了\n{traceback.format_exc()}\n原始输入:{action_kwargs}")
|
|
||||||
error(traceback.format_exc())
|
|
||||||
|
|
||||||
future.add_done_callback(_handle_future_exception)
|
future.add_done_callback(_handle_future_exception)
|
||||||
except Exception as e:
|
|
||||||
execution_error = traceback.format_exc()
|
|
||||||
execution_success = False
|
|
||||||
self.lab_logger().error(f"创建异步任务失败: {traceback.format_exc()}")
|
|
||||||
else:
|
|
||||||
##### self.lab_logger().info(f"同步执行动作 {ACTION}")
|
|
||||||
future = self._executor.submit(ACTION, **action_kwargs)
|
|
||||||
|
|
||||||
def _handle_future_exception(fut):
|
|
||||||
nonlocal execution_error, execution_success, action_return_value
|
|
||||||
try:
|
|
||||||
action_return_value = fut.result()
|
|
||||||
execution_success = True
|
|
||||||
except Exception as e:
|
|
||||||
error(f"同步任务 {ACTION.__name__} 报错了\n{traceback.format_exc()}\n原始输入:{action_kwargs}")
|
|
||||||
|
|
||||||
future.add_done_callback(_handle_future_exception)
|
|
||||||
|
|
||||||
action_type = action_value_mapping["type"]
|
action_type = action_value_mapping["type"]
|
||||||
feedback_msg_types = action_type.Feedback.get_fields_and_field_types()
|
feedback_msg_types = action_type.Feedback.get_fields_and_field_types()
|
||||||
|
|||||||
@@ -843,6 +843,15 @@ class HostNode(BaseROS2DeviceNode):
|
|||||||
success = bool(r)
|
success = bool(r)
|
||||||
|
|
||||||
response.success = success
|
response.success = success
|
||||||
|
|
||||||
|
if success:
|
||||||
|
from unilabos.resources.graphio import physical_setup_graph
|
||||||
|
for resource in resources:
|
||||||
|
if resource.get("id") not in physical_setup_graph.nodes:
|
||||||
|
physical_setup_graph.add_node(resource["id"], **resource)
|
||||||
|
else:
|
||||||
|
physical_setup_graph.nodes[resource["id"]]["data"].update(resource["data"])
|
||||||
|
|
||||||
self.lab_logger().info(f"[Host Node-Resource] Add request completed, success: {success}")
|
self.lab_logger().info(f"[Host Node-Resource] Add request completed, success: {success}")
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
|||||||
@@ -146,6 +146,7 @@ class ROS2ProtocolNode(BaseROS2DeviceNode):
|
|||||||
# 为子设备的每个动作创建动作客户端
|
# 为子设备的每个动作创建动作客户端
|
||||||
if d is not None and hasattr(d, "ros_node_instance"):
|
if d is not None and hasattr(d, "ros_node_instance"):
|
||||||
node = d.ros_node_instance
|
node = d.ros_node_instance
|
||||||
|
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("UniLabJsonCommand"):
|
if action_name.startswith("auto-") or str(action_mapping.get("type", "")).startswith("UniLabJsonCommand"):
|
||||||
continue
|
continue
|
||||||
@@ -223,9 +224,14 @@ class ROS2ProtocolNode(BaseROS2DeviceNode):
|
|||||||
|
|
||||||
self.lab_logger().info(f"Working on physical setup: {physical_setup_graph}")
|
self.lab_logger().info(f"Working on physical setup: {physical_setup_graph}")
|
||||||
protocol_steps = protocol_steps_generator(G=physical_setup_graph, **protocol_kwargs)
|
protocol_steps = protocol_steps_generator(G=physical_setup_graph, **protocol_kwargs)
|
||||||
|
logs = []
|
||||||
|
for step in protocol_steps:
|
||||||
|
if isinstance(step, dict) and "log_message" in step.get("action_kwargs", {}):
|
||||||
|
logs.append(step)
|
||||||
|
elif isinstance(step, list):
|
||||||
|
logs.append(step)
|
||||||
self.lab_logger().info(f"Goal received: {protocol_kwargs}, running steps: "
|
self.lab_logger().info(f"Goal received: {protocol_kwargs}, running steps: "
|
||||||
f"{json.dumps([step for step in protocol_steps if 'log_message' not in step['action_kwargs']], indent=4)}")
|
f"{json.dumps(logs, indent=4, ensure_ascii=False)}")
|
||||||
|
|
||||||
time_start = time.time()
|
time_start = time.time()
|
||||||
time_overall = 100
|
time_overall = 100
|
||||||
|
|||||||
@@ -37,6 +37,8 @@ class DeviceNodeResourceTracker(object):
|
|||||||
def figure_resource(self, query_resource, try_mode=False):
|
def figure_resource(self, query_resource, try_mode=False):
|
||||||
if isinstance(query_resource, list):
|
if isinstance(query_resource, list):
|
||||||
return [self.figure_resource(r) for r in query_resource]
|
return [self.figure_resource(r) for r in query_resource]
|
||||||
|
elif isinstance(query_resource, dict) and "id" not in query_resource and "name" not in query_resource: # 临时处理,要删除的,driver有太多类型错误标注
|
||||||
|
return [self.figure_resource(r) for r in query_resource.values()]
|
||||||
res_id = query_resource.id if hasattr(query_resource, "id") else (query_resource.get("id") if isinstance(query_resource, dict) else None)
|
res_id = query_resource.id if hasattr(query_resource, "id") else (query_resource.get("id") if isinstance(query_resource, dict) else None)
|
||||||
res_name = query_resource.name if hasattr(query_resource, "name") else (query_resource.get("name") if isinstance(query_resource, dict) else None)
|
res_name = query_resource.name if hasattr(query_resource, "name") else (query_resource.get("name") if isinstance(query_resource, dict) else None)
|
||||||
res_identifier = res_id if res_id else res_name
|
res_identifier = res_id if res_id else res_name
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ class DeviceClassCreator(Generic[T]):
|
|||||||
"""
|
"""
|
||||||
if self.device_instance is not None:
|
if self.device_instance is not None:
|
||||||
for c in self.children.values():
|
for c in self.children.values():
|
||||||
if c["type"] == "container":
|
if c["type"] != "device":
|
||||||
self.resource_tracker.add_resource(c)
|
self.resource_tracker.add_resource(c)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user