mirror of
https://github.com/dptech-corp/Uni-Lab-OS.git
synced 2025-12-17 04:51:10 +00:00
update registry with nested obj
This commit is contained in:
@@ -208,7 +208,6 @@ def main():
|
||||
os.path.join(os.path.dirname(os.path.dirname(__file__)), "config", "example_config.py"), config_path
|
||||
)
|
||||
print_status(f"已创建 local_config.py 路径: {config_path}", "info")
|
||||
os._exit(1)
|
||||
else:
|
||||
os._exit(1)
|
||||
# 加载配置文件
|
||||
|
||||
@@ -384,7 +384,7 @@ class MessageProcessor:
|
||||
"""停止消息处理线程"""
|
||||
self.is_running = False
|
||||
if self.thread and self.thread.is_alive():
|
||||
self.thread.join(timeout=5)
|
||||
self.thread.join(timeout=2)
|
||||
logger.info("[MessageProcessor] Stopped")
|
||||
|
||||
def _run(self):
|
||||
@@ -832,7 +832,7 @@ class QueueProcessor:
|
||||
"""停止队列处理线程"""
|
||||
self.is_running = False
|
||||
if self.thread and self.thread.is_alive():
|
||||
self.thread.join(timeout=5)
|
||||
self.thread.join(timeout=2)
|
||||
logger.info("[QueueProcessor] Stopped")
|
||||
|
||||
def _run(self):
|
||||
|
||||
@@ -94,7 +94,7 @@ serial:
|
||||
port:
|
||||
type: string
|
||||
resource_tracker:
|
||||
type: string
|
||||
type: object
|
||||
required:
|
||||
- device_id
|
||||
- port
|
||||
|
||||
@@ -63,7 +63,7 @@ camera.USB:
|
||||
default: 0.1
|
||||
type: number
|
||||
resource_tracker:
|
||||
type: string
|
||||
type: object
|
||||
required: []
|
||||
type: object
|
||||
data:
|
||||
|
||||
@@ -170,7 +170,7 @@ hplc.agilent:
|
||||
module: unilabos.devices.hplc.AgilentHPLC:HPLCDriver
|
||||
status_types:
|
||||
could_run: bool
|
||||
data_file: list
|
||||
data_file: String
|
||||
device_status: str
|
||||
driver_init_ok: bool
|
||||
finish_status: str
|
||||
@@ -195,6 +195,8 @@ hplc.agilent:
|
||||
could_run:
|
||||
type: boolean
|
||||
data_file:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
device_status:
|
||||
type: string
|
||||
|
||||
@@ -4508,14 +4508,22 @@ liquid_handler.biomek:
|
||||
bind_parent_id:
|
||||
type: string
|
||||
liquid_input_slot:
|
||||
items:
|
||||
type: integer
|
||||
type: array
|
||||
liquid_type:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
liquid_volume:
|
||||
items:
|
||||
type: integer
|
||||
type: array
|
||||
resource_tracker:
|
||||
type: string
|
||||
type: object
|
||||
resources:
|
||||
items:
|
||||
type: object
|
||||
type: array
|
||||
slot_on_deck:
|
||||
type: integer
|
||||
@@ -4559,10 +4567,16 @@ liquid_handler.biomek:
|
||||
id:
|
||||
type: string
|
||||
liquid_input_wells:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
liquid_type:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
liquid_volume:
|
||||
items:
|
||||
type: integer
|
||||
type: array
|
||||
parent:
|
||||
type: string
|
||||
@@ -6039,6 +6053,8 @@ liquid_handler.prcxi:
|
||||
properties:
|
||||
none_keys:
|
||||
default: []
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
protocol_author:
|
||||
default: ''
|
||||
@@ -6139,7 +6155,7 @@ liquid_handler.prcxi:
|
||||
default: 0
|
||||
type: number
|
||||
well:
|
||||
type: string
|
||||
type: object
|
||||
required:
|
||||
- well
|
||||
type: object
|
||||
@@ -8358,7 +8374,7 @@ liquid_handler.prcxi:
|
||||
default: false
|
||||
type: string
|
||||
deck:
|
||||
type: string
|
||||
type: object
|
||||
host:
|
||||
type: string
|
||||
matrix_id:
|
||||
|
||||
@@ -832,7 +832,7 @@ syringe_pump_with_valve.runze.SY03B-T06:
|
||||
default: 25.0
|
||||
type: number
|
||||
mode:
|
||||
type: string
|
||||
type: object
|
||||
port:
|
||||
type: string
|
||||
required:
|
||||
@@ -1352,7 +1352,7 @@ syringe_pump_with_valve.runze.SY03B-T08:
|
||||
default: 25.0
|
||||
type: number
|
||||
mode:
|
||||
type: string
|
||||
type: object
|
||||
port:
|
||||
type: string
|
||||
required:
|
||||
|
||||
@@ -133,7 +133,7 @@ robotic_arm.SCARA_with_slider.virtual:
|
||||
goal:
|
||||
properties:
|
||||
ros_node:
|
||||
type: string
|
||||
type: object
|
||||
required:
|
||||
- ros_node
|
||||
type: object
|
||||
@@ -753,7 +753,7 @@ robotic_arm.elite:
|
||||
module: unilabos.devices.arm.elite_robot:EliteRobot
|
||||
status_types:
|
||||
actual_joint_positions: String
|
||||
arm_pose: list
|
||||
arm_pose: String
|
||||
type: python
|
||||
config_info: []
|
||||
description: Elite robot arm
|
||||
@@ -775,6 +775,8 @@ robotic_arm.elite:
|
||||
actual_joint_positions:
|
||||
type: string
|
||||
arm_pose:
|
||||
items:
|
||||
type: number
|
||||
type: array
|
||||
required:
|
||||
- arm_pose
|
||||
|
||||
@@ -37,7 +37,7 @@ linear_motion.grbl:
|
||||
goal:
|
||||
properties:
|
||||
position:
|
||||
type: string
|
||||
type: object
|
||||
required:
|
||||
- position
|
||||
type: object
|
||||
@@ -450,6 +450,8 @@ linear_motion.grbl:
|
||||
- 0
|
||||
- -80
|
||||
- 0
|
||||
items:
|
||||
type: integer
|
||||
type: array
|
||||
port:
|
||||
type: string
|
||||
@@ -459,7 +461,7 @@ linear_motion.grbl:
|
||||
data:
|
||||
properties:
|
||||
position:
|
||||
type: string
|
||||
type: object
|
||||
spindle_speed:
|
||||
type: number
|
||||
status:
|
||||
@@ -605,7 +607,7 @@ linear_motion.toyo_xyz.sim:
|
||||
goal:
|
||||
properties:
|
||||
ros_node:
|
||||
type: string
|
||||
type: object
|
||||
required:
|
||||
- ros_node
|
||||
type: object
|
||||
|
||||
@@ -6129,7 +6129,7 @@ workstation:
|
||||
protocol_type:
|
||||
type: string
|
||||
resource_tracker:
|
||||
type: string
|
||||
type: object
|
||||
required:
|
||||
- device_id
|
||||
- children
|
||||
@@ -6171,14 +6171,22 @@ workstation.example:
|
||||
bind_parent_id:
|
||||
type: string
|
||||
liquid_input_slot:
|
||||
items:
|
||||
type: integer
|
||||
type: array
|
||||
liquid_type:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
liquid_volume:
|
||||
items:
|
||||
type: integer
|
||||
type: array
|
||||
resource_tracker:
|
||||
type: string
|
||||
type: object
|
||||
resources:
|
||||
items:
|
||||
type: object
|
||||
type: array
|
||||
slot_on_deck:
|
||||
type: integer
|
||||
@@ -6213,9 +6221,9 @@ workstation.example:
|
||||
goal:
|
||||
properties:
|
||||
base_plate:
|
||||
type: string
|
||||
type: object
|
||||
tip_rack:
|
||||
type: string
|
||||
type: object
|
||||
required:
|
||||
- tip_rack
|
||||
- base_plate
|
||||
@@ -6241,9 +6249,9 @@ workstation.example:
|
||||
goal:
|
||||
properties:
|
||||
from_plate:
|
||||
type: string
|
||||
type: object
|
||||
to_base_plate:
|
||||
type: string
|
||||
type: object
|
||||
required:
|
||||
- from_plate
|
||||
- to_base_plate
|
||||
@@ -6271,7 +6279,7 @@ workstation.example:
|
||||
protocol_type:
|
||||
type: string
|
||||
resource_tracker:
|
||||
type: string
|
||||
type: object
|
||||
required:
|
||||
- device_id
|
||||
- children
|
||||
|
||||
@@ -5,7 +5,7 @@ import sys
|
||||
import inspect
|
||||
import importlib
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, List
|
||||
from typing import Any, Dict, List, Union, Tuple
|
||||
|
||||
import yaml
|
||||
|
||||
@@ -294,7 +294,7 @@ class Registry:
|
||||
logger.warning(f"[UniLab Registry] 设备 {device_id} 的 {field_name} 类型为空,跳过替换")
|
||||
return type_name
|
||||
convert_manager = { # 将python基本对象转为ros2基本对象
|
||||
"str": "String",
|
||||
"builtins:str": "String",
|
||||
"bool": "Bool",
|
||||
"int": "Int64",
|
||||
"float": "Float64",
|
||||
@@ -310,37 +310,73 @@ class Registry:
|
||||
logger.error(f"[UniLab Registry] 无法找到类型 '{type_name}' 用于设备 {device_id} 的 {field_name}")
|
||||
sys.exit(1)
|
||||
|
||||
def _get_json_schema_type(self, type_str: str) -> str:
|
||||
"""
|
||||
根据类型字符串返回对应的JSON Schema类型
|
||||
|
||||
Args:
|
||||
type_str: 类型字符串
|
||||
|
||||
Returns:
|
||||
JSON Schema类型字符串
|
||||
"""
|
||||
type_lower = type_str.lower()
|
||||
type_mapping = {
|
||||
("str", "string"): "string",
|
||||
("int", "integer"): "integer",
|
||||
("float", "number"): "number",
|
||||
("bool", "boolean"): "boolean",
|
||||
("list", "array"): "array",
|
||||
("dict", "object"): "object",
|
||||
}
|
||||
|
||||
# 遍历映射找到匹配的类型
|
||||
for type_variants, json_type in type_mapping.items():
|
||||
if type_lower in type_variants:
|
||||
return json_type
|
||||
|
||||
# 特殊处理包含冒号的类型(如ROS消息类型)
|
||||
if ":" in type_lower:
|
||||
return "object"
|
||||
|
||||
# 默认返回字符串类型
|
||||
return "string"
|
||||
|
||||
def _generate_schema_from_info(
|
||||
self,
|
||||
param_name: str,
|
||||
param_type: str,
|
||||
param_type: Union[str, Tuple[str]],
|
||||
param_default: Any,
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
根据参数信息生成JSON Schema
|
||||
"""
|
||||
prop_schema = {}
|
||||
# 根据类型设置schema FIXME 不完整
|
||||
if param_type:
|
||||
param_type_lower = param_type.lower()
|
||||
if param_type_lower in ["str", "string"]:
|
||||
prop_schema["type"] = "string"
|
||||
elif param_type_lower in ["int", "integer"]:
|
||||
prop_schema["type"] = "integer"
|
||||
elif param_type_lower in ["float", "number"]:
|
||||
prop_schema["type"] = "number"
|
||||
elif param_type_lower in ["bool", "boolean"]:
|
||||
prop_schema["type"] = "boolean"
|
||||
elif param_type_lower in ["list", "array"]:
|
||||
prop_schema["type"] = "array"
|
||||
elif param_type_lower in ["dict", "object"]:
|
||||
prop_schema["type"] = "object"
|
||||
|
||||
# 处理嵌套类型(Tuple[str])
|
||||
if isinstance(param_type, tuple):
|
||||
if len(param_type) == 2:
|
||||
outer_type, inner_type = param_type
|
||||
outer_json_type = self._get_json_schema_type(outer_type)
|
||||
inner_json_type = self._get_json_schema_type(inner_type)
|
||||
|
||||
prop_schema["type"] = outer_json_type
|
||||
|
||||
# 根据外层类型设置内层类型信息
|
||||
if outer_json_type == "array":
|
||||
prop_schema["items"] = {"type": inner_json_type}
|
||||
elif outer_json_type == "object":
|
||||
prop_schema["additionalProperties"] = {"type": inner_json_type}
|
||||
else:
|
||||
# 默认为字符串类型
|
||||
# 不是标准的嵌套类型,默认为字符串
|
||||
prop_schema["type"] = "string"
|
||||
else:
|
||||
# 如果没有类型信息,默认为字符串
|
||||
prop_schema["type"] = "string"
|
||||
# 处理非嵌套类型
|
||||
if param_type:
|
||||
prop_schema["type"] = self._get_json_schema_type(param_type)
|
||||
else:
|
||||
# 如果没有类型信息,默认为字符串
|
||||
prop_schema["type"] = "string"
|
||||
|
||||
# 设置默认值
|
||||
if param_default is not None:
|
||||
@@ -456,7 +492,7 @@ class Registry:
|
||||
{k: v["return_type"] for k, v in enhanced_info["status_methods"].items()}
|
||||
)
|
||||
for status_name, status_type in device_config["class"]["status_types"].items():
|
||||
if status_type in ["Any", "None", "Unknown"]:
|
||||
if isinstance(status_type, tuple) or status_type in ["Any", "None", "Unknown"]:
|
||||
status_type = "String" # 替换成ROS的String,便于显示
|
||||
device_config["class"]["status_types"][status_name] = status_type
|
||||
target_type = self._replace_type_with_class(status_type, device_id, f"状态 {status_name}")
|
||||
|
||||
@@ -697,7 +697,7 @@ class BaseROS2DeviceNode(Node, Generic[T]):
|
||||
else:
|
||||
final_resource = [convert_resources_to_type([i], final_type)[0] for i in resources_list]
|
||||
try:
|
||||
action_kwargs[k] = self.resource_tracker.figure_resource(final_resource, try_mode=True)
|
||||
action_kwargs[k] = self.resource_tracker.figure_resource(final_resource, try_mode=False)
|
||||
except Exception as e:
|
||||
self.lab_logger().error(f"物料实例获取失败: {e}\n{traceback.format_exc()}")
|
||||
error_skip = True
|
||||
|
||||
@@ -199,22 +199,18 @@ class HostNode(BaseROS2DeviceNode):
|
||||
"children": [],
|
||||
},
|
||||
)
|
||||
resource_with_parent_name = []
|
||||
resource_with_dirs_name = []
|
||||
resource_ids_to_instance = {i["id"]: i for i in resources_config}
|
||||
resource_name_to_with_parent_name = {}
|
||||
for res in resources_config:
|
||||
# if res.get("parent") and res.get("type") == "device" and res.get("class"):
|
||||
# parent_id = res.get("parent")
|
||||
# parent_res = resource_ids_to_instance[parent_id]
|
||||
# if parent_res.get("type") == "device" and parent_res.get("class"):
|
||||
# resource_with_parent_name.append(copy.deepcopy(res))
|
||||
# resource_name_to_with_parent_name[resource_with_parent_name[-1]["id"]] = f"{parent_res['id']}/{res['id']}"
|
||||
# resource_with_parent_name[-1]["id"] = f"{parent_res['id']}/{res['id']}"
|
||||
# continue
|
||||
resource_with_parent_name.append(copy.deepcopy(res))
|
||||
# for edge in self.resources_edge_config:
|
||||
# edge["source"] = resource_name_to_with_parent_name.get(edge.get("source"), edge.get("source"))
|
||||
# edge["target"] = resource_name_to_with_parent_name.get(edge.get("target"), edge.get("target"))
|
||||
temp_res = res
|
||||
res_paths = [res]
|
||||
while temp_res.get("parent"):
|
||||
temp_res = resource_ids_to_instance[temp_res.get("parent")]
|
||||
res_paths.append(temp_res)
|
||||
dirs = "/" + "/".join([res["id"] for res in res_paths[::-1]])
|
||||
new_res = copy.deepcopy(res)
|
||||
new_res["data"]["unilabos_dirs"] = dirs
|
||||
resource_with_dirs_name.append(new_res)
|
||||
try:
|
||||
for bridge in self.bridges:
|
||||
if hasattr(bridge, "resource_add"):
|
||||
@@ -222,7 +218,12 @@ class HostNode(BaseROS2DeviceNode):
|
||||
|
||||
client: HTTPClient = bridge
|
||||
resource_start_time = time.time()
|
||||
resource_add_res = client.resource_add(add_schema(resource_with_parent_name), False)
|
||||
resource_add_res = client.resource_add(add_schema(resources_config), False)
|
||||
# DEBUG ONLY
|
||||
# for i in resource_with_dirs_name:
|
||||
# http_req = self.bridges[-1].resource_get(i["data"]["unilabos_dirs"], True)
|
||||
# res = self._resource_get_process(http_req)
|
||||
# print(res)
|
||||
resource_end_time = time.time()
|
||||
self.lab_logger().info(
|
||||
f"[Host Node-Resource] 物料上传 {round(resource_end_time - resource_start_time, 5) * 1000} ms"
|
||||
@@ -871,6 +872,12 @@ class HostNode(BaseROS2DeviceNode):
|
||||
self.lab_logger().info(f"[Host Node-Resource] Add request completed, success: {success}")
|
||||
return response
|
||||
|
||||
def _resource_get_process(self, data: Dict[str, Any]):
|
||||
r = data["data"]
|
||||
self.lab_logger().debug(f"[Host Node-Resource] Retrieved from bridge: {len(r)} resources")
|
||||
resources = [convert_to_ros_msg(Resource, resource) for resource in r]
|
||||
return resources
|
||||
|
||||
def _resource_get_callback(self, request: ResourceGet.Request, response: ResourceGet.Response):
|
||||
"""
|
||||
获取资源回调
|
||||
@@ -884,22 +891,14 @@ class HostNode(BaseROS2DeviceNode):
|
||||
Returns:
|
||||
响应对象,包含查询到的资源
|
||||
"""
|
||||
self.lab_logger().info(f"[Host Node-Resource] Get request for ID: {request.id}")
|
||||
|
||||
if len(self.bridges) > 0:
|
||||
# 云上物料服务,根据 id 查询物料
|
||||
try:
|
||||
r = self.bridges[-1].resource_get(request.id, request.with_children)["data"]
|
||||
self.lab_logger().debug(f"[Host Node-Resource] Retrieved from bridge: {len(r)} resources")
|
||||
except Exception as e:
|
||||
self.lab_logger().error(f"[Host Node-Resource] Error retrieving from bridge: {str(e)}")
|
||||
r = [resource for resource in self.resources_config if resource.get("id") == request.id]
|
||||
self.lab_logger().warning(f"[Host Node-Resource] Retrieved from local: {len(r)} resources")
|
||||
else:
|
||||
# 本地物料服务,根据 id 查询物料
|
||||
r = [resource for resource in self.resources_config if resource.get("id") == request.id]
|
||||
self.lab_logger().debug(f"[Host Node-Resource] Retrieved from local: {len(r)} resources")
|
||||
|
||||
try:
|
||||
http_req = self.bridges[-1].resource_get(request.id, request.with_children)
|
||||
response.resources = self._resource_get_process(http_req)
|
||||
return response
|
||||
except Exception as e:
|
||||
self.lab_logger().error(f"[Host Node-Resource] Error retrieving from bridge: {str(e)}")
|
||||
r = [resource for resource in self.resources_config if resource.get("id") == request.id]
|
||||
self.lab_logger().debug(f"[Host Node-Resource] Retrieved from local: {len(r)} resources")
|
||||
response.resources = [convert_to_ros_msg(Resource, resource) for resource in r]
|
||||
return response
|
||||
|
||||
|
||||
@@ -12,8 +12,7 @@ import traceback
|
||||
import ast
|
||||
import os
|
||||
from pathlib import Path
|
||||
from typing import Dict, List, Any, Optional, Callable, Type
|
||||
|
||||
from typing import Dict, List, Any, Optional, Callable, Type, Union, Tuple
|
||||
|
||||
__all__ = [
|
||||
"ImportManager",
|
||||
@@ -383,7 +382,7 @@ class ImportManager:
|
||||
signature = inspect.signature(method)
|
||||
return self._get_type_string(signature.return_annotation)
|
||||
|
||||
def _get_type_string(self, annotation) -> str:
|
||||
def _get_type_string(self, annotation) -> Union[str, Tuple[str, Any]]:
|
||||
"""将类型注解转换为Class Library中可搜索的类名"""
|
||||
if annotation == inspect.Parameter.empty:
|
||||
return "Any" # 如果没有注解,返回Any
|
||||
@@ -400,7 +399,7 @@ class ImportManager:
|
||||
return "Int64MultiArray"
|
||||
elif isinstance(arg0, float):
|
||||
return "Float64MultiArray"
|
||||
return "list"
|
||||
return "list", self._get_type_string(arg0)
|
||||
elif origin is dict:
|
||||
return "dict"
|
||||
elif origin is Optional:
|
||||
|
||||
Reference in New Issue
Block a user