mirror of
https://github.com/dptech-corp/Uni-Lab-OS.git
synced 2025-12-17 13:01:12 +00:00
feat: node_info_update srv
fix: OTDeck cant create
This commit is contained in:
@@ -58,6 +58,18 @@ def parse_args():
|
|||||||
default=None,
|
default=None,
|
||||||
help="配置文件路径,支持.py格式的Python配置文件",
|
help="配置文件路径,支持.py格式的Python配置文件",
|
||||||
)
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--port",
|
||||||
|
type=int,
|
||||||
|
default=None,
|
||||||
|
help="信息页web服务的启动端口",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--open_browser",
|
||||||
|
type=bool,
|
||||||
|
default=True,
|
||||||
|
help="是否在启动时打开信息页",
|
||||||
|
)
|
||||||
|
|
||||||
return parser.parse_args()
|
return parser.parse_args()
|
||||||
|
|
||||||
@@ -151,7 +163,7 @@ def main():
|
|||||||
mqtt_client.start()
|
mqtt_client.start()
|
||||||
|
|
||||||
start_backend(**args_dict)
|
start_backend(**args_dict)
|
||||||
start_server()
|
start_server(port=args_dict.get("port", 8002), open_browser=args_dict.get("open_browser", False))
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
@@ -92,19 +92,7 @@ def setup_web_pages(router: APIRouter) -> None:
|
|||||||
|
|
||||||
# 获取已加载的设备
|
# 获取已加载的设备
|
||||||
if lab_registry:
|
if lab_registry:
|
||||||
# 设备类型
|
devices = lab_registry.obtain_registry_device_info()
|
||||||
for device_id, device_info in lab_registry.device_type_registry.items():
|
|
||||||
msg = {
|
|
||||||
"id": device_id,
|
|
||||||
"name": device_info.get("name", "未命名"),
|
|
||||||
"file_path": device_info.get("file_path", ""),
|
|
||||||
"class_json": json.dumps(
|
|
||||||
device_info.get("class", {}), indent=4, ensure_ascii=False, cls=TypeEncoder
|
|
||||||
),
|
|
||||||
}
|
|
||||||
mqtt_client.publish_registry(device_id, device_info)
|
|
||||||
devices.append(msg)
|
|
||||||
|
|
||||||
# 资源类型
|
# 资源类型
|
||||||
for resource_id, resource_info in lab_registry.resource_type_registry.items():
|
for resource_id, resource_info in lab_registry.resource_type_registry.items():
|
||||||
resources.append(
|
resources.append(
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import json
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
@@ -8,6 +9,7 @@ import yaml
|
|||||||
from unilabos.utils import logger
|
from unilabos.utils import logger
|
||||||
from unilabos.ros.msgs.message_converter import msg_converter_manager
|
from unilabos.ros.msgs.message_converter import msg_converter_manager
|
||||||
from unilabos.utils.decorator import singleton
|
from unilabos.utils.decorator import singleton
|
||||||
|
from unilabos.utils.type_check import TypeEncoder
|
||||||
|
|
||||||
DEFAULT_PATHS = [Path(__file__).absolute().parent]
|
DEFAULT_PATHS = [Path(__file__).absolute().parent]
|
||||||
|
|
||||||
@@ -143,6 +145,20 @@ class Registry:
|
|||||||
f"[UniLab Registry] Device File-{i+1}/{len(files)} Not Valid YAML File: {file.absolute()}"
|
f"[UniLab Registry] Device File-{i+1}/{len(files)} Not Valid YAML File: {file.absolute()}"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def obtain_registry_device_info(self):
|
||||||
|
devices = []
|
||||||
|
for device_id, device_info in self.device_type_registry.items():
|
||||||
|
msg = {
|
||||||
|
"id": device_id,
|
||||||
|
"name": device_info.get("name", "未命名"),
|
||||||
|
"file_path": device_info.get("file_path", ""),
|
||||||
|
"class_json": json.dumps(
|
||||||
|
device_info.get("class", {}), indent=4, ensure_ascii=False, cls=TypeEncoder
|
||||||
|
),
|
||||||
|
}
|
||||||
|
devices.append(msg)
|
||||||
|
return devices
|
||||||
|
|
||||||
|
|
||||||
# 全局单例实例
|
# 全局单例实例
|
||||||
lab_registry = Registry()
|
lab_registry = Registry()
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import importlib
|
import importlib
|
||||||
|
import inspect
|
||||||
import json
|
import json
|
||||||
from typing import Union
|
from typing import Union
|
||||||
import numpy as np
|
import numpy as np
|
||||||
@@ -384,7 +385,11 @@ def resource_ulab_to_plr(resource: dict, plr_model=False) -> "ResourcePLR":
|
|||||||
d = resource_ulab_to_plr_inner(resource)
|
d = resource_ulab_to_plr_inner(resource)
|
||||||
"""无法通过Resource进行反序列化,例如TipSpot必须内部序列化好,直接用TipSpot序列化会多参数,导致出错"""
|
"""无法通过Resource进行反序列化,例如TipSpot必须内部序列化好,直接用TipSpot序列化会多参数,导致出错"""
|
||||||
from pylabrobot.utils.object_parsing import find_subclass
|
from pylabrobot.utils.object_parsing import find_subclass
|
||||||
resource_plr = find_subclass(d["type"], ResourcePLR).deserialize(d, allow_marshal=True)
|
sub_cls = find_subclass(d["type"], ResourcePLR)
|
||||||
|
spect = inspect.signature(sub_cls)
|
||||||
|
if "category" not in spect.parameters:
|
||||||
|
d.pop("category")
|
||||||
|
resource_plr = sub_cls.deserialize(d, allow_marshal=True)
|
||||||
resource_plr.load_all_state(all_states)
|
resource_plr.load_all_state(all_states)
|
||||||
return resource_plr
|
return resource_plr
|
||||||
|
|
||||||
|
|||||||
@@ -1,14 +1,16 @@
|
|||||||
|
import json
|
||||||
import os
|
import os
|
||||||
import traceback
|
import traceback
|
||||||
from typing import Optional, Dict, Any, List
|
from typing import Optional, Dict, Any, List
|
||||||
|
|
||||||
import rclpy
|
import rclpy
|
||||||
from unilabos_msgs.msg import Resource # type: ignore
|
from unilabos_msgs.msg import Resource # type: ignore
|
||||||
from unilabos_msgs.srv import ResourceAdd # type: ignore
|
from unilabos_msgs.srv import ResourceAdd, SerialCommand # type: ignore
|
||||||
from rclpy.executors import MultiThreadedExecutor
|
from rclpy.executors import MultiThreadedExecutor
|
||||||
from rclpy.node import Node
|
from rclpy.node import Node
|
||||||
from rclpy.timer import Timer
|
from rclpy.timer import Timer
|
||||||
|
|
||||||
|
from unilabos.registry.registry import lab_registry
|
||||||
from unilabos.ros.initialize_device import initialize_device_from_dict
|
from unilabos.ros.initialize_device import initialize_device_from_dict
|
||||||
from unilabos.ros.msgs.message_converter import (
|
from unilabos.ros.msgs.message_converter import (
|
||||||
convert_to_ros_msg,
|
convert_to_ros_msg,
|
||||||
@@ -17,6 +19,7 @@ from unilabos.ros.nodes.presets.host_node import HostNode
|
|||||||
from unilabos.ros.x.rclpyx import run_event_loop_in_thread
|
from unilabos.ros.x.rclpyx import run_event_loop_in_thread
|
||||||
from unilabos.utils import logger
|
from unilabos.utils import logger
|
||||||
from unilabos.config.config import BasicConfig
|
from unilabos.config.config import BasicConfig
|
||||||
|
from unilabos.utils.type_check import TypeEncoder
|
||||||
|
|
||||||
|
|
||||||
def exit() -> None:
|
def exit() -> None:
|
||||||
@@ -98,17 +101,29 @@ def slave(
|
|||||||
n = Node(f"slaveMachine_{machine_name}", parameter_overrides=[])
|
n = Node(f"slaveMachine_{machine_name}", parameter_overrides=[])
|
||||||
executor.add_node(n)
|
executor.add_node(n)
|
||||||
|
|
||||||
if BasicConfig.slave_no_host:
|
if not BasicConfig.slave_no_host:
|
||||||
# 确保ResourceAdd存在
|
sclient = n.create_client(SerialCommand, "/node_info_update")
|
||||||
if "ResourceAdd" in globals():
|
sclient.wait_for_service()
|
||||||
|
|
||||||
|
request = SerialCommand.Request()
|
||||||
|
request.command = json.dumps({
|
||||||
|
"machine_name": machine_name,
|
||||||
|
"type": "slave",
|
||||||
|
"devices_config": devices_config,
|
||||||
|
"registry_config": lab_registry.obtain_registry_device_info()
|
||||||
|
}, ensure_ascii=False, cls=TypeEncoder)
|
||||||
|
response = sclient.call_async(request)
|
||||||
|
logger.info(f"Slave node info update response: {response}")
|
||||||
|
|
||||||
rclient = n.create_client(ResourceAdd, "/resources/add")
|
rclient = n.create_client(ResourceAdd, "/resources/add")
|
||||||
rclient.wait_for_service() # FIXME 可能一直等待,加一个参数
|
rclient.wait_for_service() # FIXME 可能一直等待,加一个参数
|
||||||
|
|
||||||
request = ResourceAdd.Request()
|
request = ResourceAdd.Request()
|
||||||
request.resources = [convert_to_ros_msg(Resource, resource) for resource in resources_config]
|
request.resources = [convert_to_ros_msg(Resource, resource) for resource in resources_config]
|
||||||
response = rclient.call_async(request)
|
response = rclient.call_async(request)
|
||||||
else:
|
logger.info(f"Slave resource add response: {response}")
|
||||||
print("Warning: ResourceAdd service not available")
|
|
||||||
|
|
||||||
|
|
||||||
run_event_loop_in_thread()
|
run_event_loop_in_thread()
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import copy
|
import copy
|
||||||
|
import json
|
||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
import traceback
|
import traceback
|
||||||
@@ -7,7 +8,7 @@ from typing import Optional, Dict, Any, List, ClassVar, Set
|
|||||||
|
|
||||||
from action_msgs.msg import GoalStatus
|
from action_msgs.msg import GoalStatus
|
||||||
from unilabos_msgs.msg import Resource # type: ignore
|
from unilabos_msgs.msg import Resource # type: ignore
|
||||||
from unilabos_msgs.srv import ResourceAdd, ResourceGet, ResourceDelete, ResourceUpdate, ResourceList # type: ignore
|
from unilabos_msgs.srv import ResourceAdd, ResourceGet, ResourceDelete, ResourceUpdate, ResourceList, SerialCommand # type: ignore
|
||||||
from rclpy.action import ActionClient, get_action_server_names_and_types_by_node
|
from rclpy.action import ActionClient, get_action_server_names_and_types_by_node
|
||||||
from rclpy.callback_groups import ReentrantCallbackGroup
|
from rclpy.callback_groups import ReentrantCallbackGroup
|
||||||
from rclpy.service import Service
|
from rclpy.service import Service
|
||||||
@@ -20,7 +21,8 @@ from unilabos.ros.msgs.message_converter import (
|
|||||||
get_ros_type_by_msgname,
|
get_ros_type_by_msgname,
|
||||||
convert_from_ros_msg,
|
convert_from_ros_msg,
|
||||||
convert_to_ros_msg,
|
convert_to_ros_msg,
|
||||||
msg_converter_manager, ros_action_to_json_schema,
|
msg_converter_manager,
|
||||||
|
ros_action_to_json_schema,
|
||||||
)
|
)
|
||||||
from unilabos.ros.nodes.base_device_node import BaseROS2DeviceNode, ROS2DeviceNode, DeviceNodeResourceTracker
|
from unilabos.ros.nodes.base_device_node import BaseROS2DeviceNode, ROS2DeviceNode, DeviceNodeResourceTracker
|
||||||
from unilabos.ros.nodes.presets.controller_node import ControllerNode
|
from unilabos.ros.nodes.presets.controller_node import ControllerNode
|
||||||
@@ -106,7 +108,7 @@ class HostNode(BaseROS2DeviceNode):
|
|||||||
self._subscribed_topics = set() # 用于跟踪已订阅的话题
|
self._subscribed_topics = set() # 用于跟踪已订阅的话题
|
||||||
|
|
||||||
# 创建物料增删改查服务(非客户端)
|
# 创建物料增删改查服务(非客户端)
|
||||||
self._init_resource_service()
|
self._init_host_service()
|
||||||
|
|
||||||
self.device_status = {} # 用来存储设备状态
|
self.device_status = {} # 用来存储设备状态
|
||||||
self.device_status_timestamps = {} # 用来存储设备状态最后更新时间
|
self.device_status_timestamps = {} # 用来存储设备状态最后更新时间
|
||||||
@@ -117,7 +119,9 @@ class HostNode(BaseROS2DeviceNode):
|
|||||||
# 初始化所有本机设备节点,多一次过滤,防止重复初始化
|
# 初始化所有本机设备节点,多一次过滤,防止重复初始化
|
||||||
for device_id, device_config in devices_config.items():
|
for device_id, device_config in devices_config.items():
|
||||||
if device_config.get("type", "device") != "device":
|
if device_config.get("type", "device") != "device":
|
||||||
self.lab_logger().debug(f"[Host Node] Skipping type {device_config['type']} {device_id} already existed, skipping.")
|
self.lab_logger().debug(
|
||||||
|
f"[Host Node] Skipping type {device_config['type']} {device_id} already existed, skipping."
|
||||||
|
)
|
||||||
continue
|
continue
|
||||||
if device_id not in self.devices_names:
|
if device_id not in self.devices_names:
|
||||||
self.initialize_device(device_id, device_config)
|
self.initialize_device(device_id, device_config)
|
||||||
@@ -226,6 +230,7 @@ class HostNode(BaseROS2DeviceNode):
|
|||||||
)
|
)
|
||||||
self.lab_logger().debug(f"[Host Node] Created ActionClient: {action_id}")
|
self.lab_logger().debug(f"[Host Node] Created ActionClient: {action_id}")
|
||||||
from unilabos.app.mq import mqtt_client
|
from unilabos.app.mq import mqtt_client
|
||||||
|
|
||||||
info_with_schema = ros_action_to_json_schema(action_type)
|
info_with_schema = ros_action_to_json_schema(action_type)
|
||||||
mqtt_client.publish_actions(action_id, info_with_schema)
|
mqtt_client.publish_actions(action_id, info_with_schema)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -259,6 +264,7 @@ class HostNode(BaseROS2DeviceNode):
|
|||||||
self._action_clients[action_id] = ActionClient(self, action_type, action_id)
|
self._action_clients[action_id] = ActionClient(self, action_type, action_id)
|
||||||
self.lab_logger().debug(f"[Host Node] Created ActionClient: {action_id}")
|
self.lab_logger().debug(f"[Host Node] Created ActionClient: {action_id}")
|
||||||
from unilabos.app.mq import mqtt_client
|
from unilabos.app.mq import mqtt_client
|
||||||
|
|
||||||
info_with_schema = ros_action_to_json_schema(action_type)
|
info_with_schema = ros_action_to_json_schema(action_type)
|
||||||
mqtt_client.publish_actions(action_id, info_with_schema)
|
mqtt_client.publish_actions(action_id, info_with_schema)
|
||||||
else:
|
else:
|
||||||
@@ -473,7 +479,7 @@ class HostNode(BaseROS2DeviceNode):
|
|||||||
|
|
||||||
"""Resource"""
|
"""Resource"""
|
||||||
|
|
||||||
def _init_resource_service(self):
|
def _init_host_service(self):
|
||||||
self._resource_services: Dict[str, Service] = {
|
self._resource_services: Dict[str, Service] = {
|
||||||
"resource_add": self.create_service(
|
"resource_add": self.create_service(
|
||||||
ResourceAdd, "/resources/add", self._resource_add_callback, callback_group=ReentrantCallbackGroup()
|
ResourceAdd, "/resources/add", self._resource_add_callback, callback_group=ReentrantCallbackGroup()
|
||||||
@@ -496,8 +502,28 @@ class HostNode(BaseROS2DeviceNode):
|
|||||||
"resource_list": self.create_service(
|
"resource_list": self.create_service(
|
||||||
ResourceList, "/resources/list", self._resource_list_callback, callback_group=ReentrantCallbackGroup()
|
ResourceList, "/resources/list", self._resource_list_callback, callback_group=ReentrantCallbackGroup()
|
||||||
),
|
),
|
||||||
|
"node_info_update": self.create_service(
|
||||||
|
SerialCommand,
|
||||||
|
"/node_info_update",
|
||||||
|
self._node_info_update_callback,
|
||||||
|
callback_group=ReentrantCallbackGroup(),
|
||||||
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def _node_info_update_callback(self, request, response):
|
||||||
|
"""
|
||||||
|
更新节点信息回调
|
||||||
|
"""
|
||||||
|
self.lab_logger().info(f"[Host Node] Node info update request received: {request}")
|
||||||
|
try:
|
||||||
|
info = json.loads(request.command)
|
||||||
|
self.lab_logger().info(f"[Host Node] Node info update: {info}")
|
||||||
|
response.response = "OK"
|
||||||
|
except Exception as e:
|
||||||
|
self.lab_logger().error(f"[Host Node] Error updating node info: {str(e)}")
|
||||||
|
response.response = "ERROR"
|
||||||
|
return response
|
||||||
|
|
||||||
def _resource_add_callback(self, request, response):
|
def _resource_add_callback(self, request, response):
|
||||||
"""
|
"""
|
||||||
添加资源回调
|
添加资源回调
|
||||||
|
|||||||
Reference in New Issue
Block a user