mirror of
https://github.com/dptech-corp/Uni-Lab-OS.git
synced 2026-02-04 05:15:10 +00:00
Upgrade to py 3.11.14; ros 0.7; unilabos 0.10.16
This commit is contained in:
44
.github/workflows/ci-check.yml
vendored
44
.github/workflows/ci-check.yml
vendored
@@ -36,21 +36,61 @@ jobs:
|
|||||||
# Install all packages together for proper dependency resolution
|
# Install all packages together for proper dependency resolution
|
||||||
# Use mamba for faster and more reliable solving
|
# Use mamba for faster and more reliable solving
|
||||||
mamba install -n check-env \
|
mamba install -n check-env \
|
||||||
python=3.11.11 \
|
python=3.11.14 \
|
||||||
robostack-staging::ros-humble-ros-core \
|
robostack-staging::ros-humble-ros-core \
|
||||||
robostack-staging::ros-humble-action-msgs \
|
robostack-staging::ros-humble-action-msgs \
|
||||||
robostack-staging::ros-humble-std-msgs \
|
robostack-staging::ros-humble-std-msgs \
|
||||||
robostack-staging::ros-humble-geometry-msgs \
|
robostack-staging::ros-humble-geometry-msgs \
|
||||||
robostack-staging::ros-humble-control-msgs \
|
robostack-staging::ros-humble-control-msgs \
|
||||||
|
robostack-staging::ros-humble-nav2-msgs \
|
||||||
uni-lab::ros-humble-unilabos-msgs \
|
uni-lab::ros-humble-unilabos-msgs \
|
||||||
|
robostack-staging::ros-humble-cv-bridge \
|
||||||
|
robostack-staging::ros-humble-vision-opencv \
|
||||||
|
robostack-staging::ros-humble-tf-transformations \
|
||||||
|
robostack-staging::ros-humble-moveit-msgs \
|
||||||
|
robostack-staging::ros-humble-tf2-ros \
|
||||||
|
robostack-staging::ros-humble-tf2-ros-py \
|
||||||
|
conda-forge::transforms3d \
|
||||||
-c robostack-staging -c conda-forge -c uni-lab -y
|
-c robostack-staging -c conda-forge -c uni-lab -y
|
||||||
|
|
||||||
- name: Install unilabos project
|
- name: Install pip dependencies and unilabos
|
||||||
run: |
|
run: |
|
||||||
|
# Activate the environment
|
||||||
|
conda activate check-env
|
||||||
|
|
||||||
|
# Core dependencies for devices
|
||||||
|
pip install uv
|
||||||
|
uv pip install networkx \
|
||||||
|
typing_extensions \
|
||||||
|
websockets \
|
||||||
|
msgcenterpy \
|
||||||
|
opentrons_shared_data \
|
||||||
|
pint \
|
||||||
|
fastapi \
|
||||||
|
jinja2 \
|
||||||
|
requests \
|
||||||
|
uvicorn \
|
||||||
|
git+https://github.com/Xuwznln/pylabrobot.git \
|
||||||
|
opencv-python \
|
||||||
|
pyautogui \
|
||||||
|
opcua \
|
||||||
|
pyserial \
|
||||||
|
pandas \
|
||||||
|
crcmod-plus \
|
||||||
|
pymodbus \
|
||||||
|
pywinauto_recorder \
|
||||||
|
matplotlib \
|
||||||
|
|
||||||
|
|
||||||
|
# PyLabRobot (custom fork)
|
||||||
|
pip install
|
||||||
|
|
||||||
|
# Install unilabos in editable mode
|
||||||
pip install -e .
|
pip install -e .
|
||||||
|
|
||||||
- name: Run check mode (complete_registry)
|
- name: Run check mode (complete_registry)
|
||||||
run: |
|
run: |
|
||||||
|
conda activate check-env
|
||||||
python -m unilabos --check_mode --skip_env_check
|
python -m unilabos --check_mode --skip_env_check
|
||||||
|
|
||||||
- name: Check for uncommitted changes
|
- name: Check for uncommitted changes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
package:
|
package:
|
||||||
name: ros-humble-unilabos-msgs
|
name: ros-humble-unilabos-msgs
|
||||||
version: 0.10.15
|
version: 0.10.16
|
||||||
source:
|
source:
|
||||||
path: ../../unilabos_msgs
|
path: ../../unilabos_msgs
|
||||||
target_directory: src
|
target_directory: src
|
||||||
@@ -25,7 +25,7 @@ requirements:
|
|||||||
build:
|
build:
|
||||||
- ${{ compiler('cxx') }}
|
- ${{ compiler('cxx') }}
|
||||||
- ${{ compiler('c') }}
|
- ${{ compiler('c') }}
|
||||||
- python ==3.11.11
|
- python ==3.11.14
|
||||||
- numpy
|
- numpy
|
||||||
- if: build_platform != target_platform
|
- if: build_platform != target_platform
|
||||||
then:
|
then:
|
||||||
@@ -63,14 +63,14 @@ requirements:
|
|||||||
- robostack-staging::ros-humble-rosidl-default-generators
|
- robostack-staging::ros-humble-rosidl-default-generators
|
||||||
- robostack-staging::ros-humble-std-msgs
|
- robostack-staging::ros-humble-std-msgs
|
||||||
- robostack-staging::ros-humble-geometry-msgs
|
- robostack-staging::ros-humble-geometry-msgs
|
||||||
- robostack-staging::ros2-distro-mutex=0.6
|
- robostack-staging::ros2-distro-mutex=0.7
|
||||||
run:
|
run:
|
||||||
- robostack-staging::ros-humble-action-msgs
|
- robostack-staging::ros-humble-action-msgs
|
||||||
- robostack-staging::ros-humble-ros-workspace
|
- robostack-staging::ros-humble-ros-workspace
|
||||||
- robostack-staging::ros-humble-rosidl-default-runtime
|
- robostack-staging::ros-humble-rosidl-default-runtime
|
||||||
- robostack-staging::ros-humble-std-msgs
|
- robostack-staging::ros-humble-std-msgs
|
||||||
- robostack-staging::ros-humble-geometry-msgs
|
- robostack-staging::ros-humble-geometry-msgs
|
||||||
- robostack-staging::ros2-distro-mutex=0.6
|
- robostack-staging::ros2-distro-mutex=0.7
|
||||||
- if: osx and x86_64
|
- if: osx and x86_64
|
||||||
then:
|
then:
|
||||||
- __osx >=${{ MACOSX_DEPLOYMENT_TARGET|default('10.14') }}
|
- __osx >=${{ MACOSX_DEPLOYMENT_TARGET|default('10.14') }}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
package:
|
package:
|
||||||
name: unilabos
|
name: unilabos
|
||||||
version: "0.10.15"
|
version: "0.10.16"
|
||||||
|
|
||||||
source:
|
source:
|
||||||
path: ../..
|
path: ../..
|
||||||
|
|||||||
2
setup.py
2
setup.py
@@ -4,7 +4,7 @@ package_name = 'unilabos'
|
|||||||
|
|
||||||
setup(
|
setup(
|
||||||
name=package_name,
|
name=package_name,
|
||||||
version='0.10.15',
|
version='0.10.16',
|
||||||
packages=find_packages(),
|
packages=find_packages(),
|
||||||
include_package_data=True,
|
include_package_data=True,
|
||||||
install_requires=['setuptools'],
|
install_requires=['setuptools'],
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
__version__ = "0.10.15"
|
__version__ = "0.10.16"
|
||||||
|
|||||||
6
unilabos/__main__.py
Normal file
6
unilabos/__main__.py
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
"""Entry point for `python -m unilabos`."""
|
||||||
|
|
||||||
|
from unilabos.app.main import main
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -23,7 +23,7 @@ from typing import Optional, Dict, Any, List
|
|||||||
from urllib.parse import urlparse
|
from urllib.parse import urlparse
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
|
|
||||||
from jedi.inference.gradual.typing import TypedDict
|
from typing_extensions import TypedDict
|
||||||
|
|
||||||
from unilabos.app.model import JobAddReq
|
from unilabos.app.model import JobAddReq
|
||||||
from unilabos.ros.nodes.presets.host_node import HostNode
|
from unilabos.ros.nodes.presets.host_node import HostNode
|
||||||
@@ -495,8 +495,12 @@ class MessageProcessor:
|
|||||||
await self._process_message(message_type, message_data)
|
await self._process_message(message_type, message_data)
|
||||||
else:
|
else:
|
||||||
if message_type.endswith("_material"):
|
if message_type.endswith("_material"):
|
||||||
logger.trace(f"[MessageProcessor] 收到一条归属 {data.get('edge_session')} 的旧消息:{data}")
|
logger.trace(
|
||||||
logger.debug(f"[MessageProcessor] 跳过了一条归属 {data.get('edge_session')} 的旧消息: {data.get('action')}")
|
f"[MessageProcessor] 收到一条归属 {data.get('edge_session')} 的旧消息:{data}"
|
||||||
|
)
|
||||||
|
logger.debug(
|
||||||
|
f"[MessageProcessor] 跳过了一条归属 {data.get('edge_session')} 的旧消息: {data.get('action')}"
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
await self._process_message(message_type, message_data)
|
await self._process_message(message_type, message_data)
|
||||||
except json.JSONDecodeError:
|
except json.JSONDecodeError:
|
||||||
@@ -848,9 +852,7 @@ class MessageProcessor:
|
|||||||
device_action_groups[key_add] = []
|
device_action_groups[key_add] = []
|
||||||
device_action_groups[key_add].append(item["uuid"])
|
device_action_groups[key_add].append(item["uuid"])
|
||||||
|
|
||||||
logger.info(
|
logger.info(f"[资源同步] 跨站Transfer: {item['uuid'][:8]} from {device_old_id} to {device_id}")
|
||||||
f"[资源同步] 跨站Transfer: {item['uuid'][:8]} from {device_old_id} to {device_id}"
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
# 正常update
|
# 正常update
|
||||||
key = (device_id, "update")
|
key = (device_id, "update")
|
||||||
@@ -864,7 +866,9 @@ class MessageProcessor:
|
|||||||
device_action_groups[key] = []
|
device_action_groups[key] = []
|
||||||
device_action_groups[key].append(item["uuid"])
|
device_action_groups[key].append(item["uuid"])
|
||||||
|
|
||||||
logger.trace(f"[资源同步] 动作 {action} 分组数量: {len(device_action_groups)}, 总数量: {len(resource_uuid_list)}")
|
logger.trace(
|
||||||
|
f"[资源同步] 动作 {action} 分组数量: {len(device_action_groups)}, 总数量: {len(resource_uuid_list)}"
|
||||||
|
)
|
||||||
|
|
||||||
# 为每个(device_id, action)创建独立的更新线程
|
# 为每个(device_id, action)创建独立的更新线程
|
||||||
for (device_id, actual_action), items in device_action_groups.items():
|
for (device_id, actual_action), items in device_action_groups.items():
|
||||||
@@ -912,13 +916,13 @@ class MessageProcessor:
|
|||||||
|
|
||||||
# 发送确认消息
|
# 发送确认消息
|
||||||
if self.websocket_client:
|
if self.websocket_client:
|
||||||
await self.websocket_client.send_message({
|
await self.websocket_client.send_message(
|
||||||
"action": "restart_acknowledged",
|
{"action": "restart_acknowledged", "data": {"reason": reason, "delay": delay}}
|
||||||
"data": {"reason": reason, "delay": delay}
|
)
|
||||||
})
|
|
||||||
|
|
||||||
# 设置全局重启标志
|
# 设置全局重启标志
|
||||||
import unilabos.app.main as main_module
|
import unilabos.app.main as main_module
|
||||||
|
|
||||||
main_module._restart_requested = True
|
main_module._restart_requested = True
|
||||||
main_module._restart_reason = reason
|
main_module._restart_reason = reason
|
||||||
|
|
||||||
@@ -928,10 +932,12 @@ class MessageProcessor:
|
|||||||
# 在新线程中执行清理,避免阻塞当前事件循环
|
# 在新线程中执行清理,避免阻塞当前事件循环
|
||||||
def do_cleanup():
|
def do_cleanup():
|
||||||
import time
|
import time
|
||||||
|
|
||||||
time.sleep(0.5) # 给当前消息处理完成的时间
|
time.sleep(0.5) # 给当前消息处理完成的时间
|
||||||
logger.info(f"[MessageProcessor] Starting cleanup for restart, reason: {reason}")
|
logger.info(f"[MessageProcessor] Starting cleanup for restart, reason: {reason}")
|
||||||
try:
|
try:
|
||||||
from unilabos.app.utils import cleanup_for_restart
|
from unilabos.app.utils import cleanup_for_restart
|
||||||
|
|
||||||
if cleanup_for_restart():
|
if cleanup_for_restart():
|
||||||
logger.info("[MessageProcessor] Cleanup successful, main() will restart")
|
logger.info("[MessageProcessor] Cleanup successful, main() will restart")
|
||||||
else:
|
else:
|
||||||
@@ -1382,7 +1388,9 @@ class WebSocketClient(BaseCommunicationClient):
|
|||||||
if host_node:
|
if host_node:
|
||||||
# 获取设备信息
|
# 获取设备信息
|
||||||
for device_id, namespace in host_node.devices_names.items():
|
for device_id, namespace in host_node.devices_names.items():
|
||||||
device_key = f"{namespace}/{device_id}" if namespace.startswith("/") else f"/{namespace}/{device_id}"
|
device_key = (
|
||||||
|
f"{namespace}/{device_id}" if namespace.startswith("/") else f"/{namespace}/{device_id}"
|
||||||
|
)
|
||||||
is_online = device_key in host_node._online_devices
|
is_online = device_key in host_node._online_devices
|
||||||
|
|
||||||
# 获取设备的动作信息
|
# 获取设备的动作信息
|
||||||
@@ -1396,14 +1404,16 @@ class WebSocketClient(BaseCommunicationClient):
|
|||||||
"action_type": str(type(client).__name__),
|
"action_type": str(type(client).__name__),
|
||||||
}
|
}
|
||||||
|
|
||||||
devices.append({
|
devices.append(
|
||||||
"device_id": device_id,
|
{
|
||||||
"namespace": namespace,
|
"device_id": device_id,
|
||||||
"device_key": device_key,
|
"namespace": namespace,
|
||||||
"is_online": is_online,
|
"device_key": device_key,
|
||||||
"machine_name": host_node.device_machine_names.get(device_id, machine_name),
|
"is_online": is_online,
|
||||||
"actions": actions,
|
"machine_name": host_node.device_machine_names.get(device_id, machine_name),
|
||||||
})
|
"actions": actions,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
logger.info(f"[WebSocketClient] Collected {len(devices)} devices for host_ready")
|
logger.info(f"[WebSocketClient] Collected {len(devices)} devices for host_ready")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|||||||
@@ -49,7 +49,6 @@ from unilabos.resources.resource_tracker import (
|
|||||||
ResourceTreeInstance,
|
ResourceTreeInstance,
|
||||||
ResourceDictInstance,
|
ResourceDictInstance,
|
||||||
)
|
)
|
||||||
from unilabos.ros.x.rclpyx import get_event_loop
|
|
||||||
from unilabos.ros.utils.driver_creator import WorkstationNodeCreator, PyLabRobotCreator, DeviceClassCreator
|
from unilabos.ros.utils.driver_creator import WorkstationNodeCreator, PyLabRobotCreator, DeviceClassCreator
|
||||||
from rclpy.task import Task, Future
|
from rclpy.task import Task, Future
|
||||||
from unilabos.utils.import_manager import default_manager
|
from unilabos.utils.import_manager import default_manager
|
||||||
@@ -185,7 +184,7 @@ class PropertyPublisher:
|
|||||||
f"创建发布者 {name} 失败,可能由于注册表有误,类型: {msg_type},错误: {ex}\n{traceback.format_exc()}"
|
f"创建发布者 {name} 失败,可能由于注册表有误,类型: {msg_type},错误: {ex}\n{traceback.format_exc()}"
|
||||||
)
|
)
|
||||||
self.timer = node.create_timer(self.timer_period, self.publish_property)
|
self.timer = node.create_timer(self.timer_period, self.publish_property)
|
||||||
self.__loop = get_event_loop()
|
self.__loop = ROS2DeviceNode.get_asyncio_loop()
|
||||||
str_msg_type = str(msg_type)[8:-2]
|
str_msg_type = str(msg_type)[8:-2]
|
||||||
self.node.lab_logger().trace(f"发布属性: {name}, 类型: {str_msg_type}, 周期: {initial_period}秒, QoS: {qos}")
|
self.node.lab_logger().trace(f"发布属性: {name}, 类型: {str_msg_type}, 周期: {initial_period}秒, QoS: {qos}")
|
||||||
|
|
||||||
@@ -1757,6 +1756,15 @@ class ROS2DeviceNode:
|
|||||||
它不继承设备类,而是通过代理模式访问设备类的属性和方法。
|
它不继承设备类,而是通过代理模式访问设备类的属性和方法。
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
# 类变量,用于循环管理
|
||||||
|
_asyncio_loop = None
|
||||||
|
_asyncio_loop_running = False
|
||||||
|
_asyncio_loop_thread = None
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_asyncio_loop(cls):
|
||||||
|
return cls._asyncio_loop
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
async def safe_task_wrapper(trace_callback, func, **kwargs):
|
async def safe_task_wrapper(trace_callback, func, **kwargs):
|
||||||
try:
|
try:
|
||||||
@@ -1833,6 +1841,11 @@ class ROS2DeviceNode:
|
|||||||
print_publish: 是否打印发布信息
|
print_publish: 是否打印发布信息
|
||||||
driver_is_ros:
|
driver_is_ros:
|
||||||
"""
|
"""
|
||||||
|
# 在初始化时检查循环状态
|
||||||
|
if ROS2DeviceNode._asyncio_loop_running and ROS2DeviceNode._asyncio_loop_thread is not None:
|
||||||
|
pass
|
||||||
|
elif ROS2DeviceNode._asyncio_loop_thread is None:
|
||||||
|
self._start_loop()
|
||||||
|
|
||||||
# 保存设备类是否支持异步上下文
|
# 保存设备类是否支持异步上下文
|
||||||
self._has_async_context = hasattr(driver_class, "__aenter__") and hasattr(driver_class, "__aexit__")
|
self._has_async_context = hasattr(driver_class, "__aenter__") and hasattr(driver_class, "__aexit__")
|
||||||
@@ -1924,6 +1937,17 @@ class ROS2DeviceNode:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
self._ros_node.lab_logger().error(f"设备后初始化失败: {e}")
|
self._ros_node.lab_logger().error(f"设备后初始化失败: {e}")
|
||||||
|
|
||||||
|
def _start_loop(self):
|
||||||
|
def run_event_loop():
|
||||||
|
loop = asyncio.new_event_loop()
|
||||||
|
ROS2DeviceNode._asyncio_loop = loop
|
||||||
|
asyncio.set_event_loop(loop)
|
||||||
|
loop.run_forever()
|
||||||
|
|
||||||
|
ROS2DeviceNode._asyncio_loop_thread = threading.Thread(target=run_event_loop, daemon=True, name="ROS2DeviceNode")
|
||||||
|
ROS2DeviceNode._asyncio_loop_thread.start()
|
||||||
|
logger.info(f"循环线程已启动")
|
||||||
|
|
||||||
|
|
||||||
class DeviceInfoType(TypedDict):
|
class DeviceInfoType(TypedDict):
|
||||||
id: str
|
id: str
|
||||||
|
|||||||
@@ -1,182 +0,0 @@
|
|||||||
import asyncio
|
|
||||||
from asyncio import events
|
|
||||||
import threading
|
|
||||||
|
|
||||||
import rclpy
|
|
||||||
from rclpy.impl.implementation_singleton import rclpy_implementation as _rclpy
|
|
||||||
from rclpy.executors import await_or_execute, Executor
|
|
||||||
from rclpy.action import ActionClient, ActionServer
|
|
||||||
from rclpy.action.server import ServerGoalHandle, GoalResponse, GoalInfo, GoalStatus
|
|
||||||
from std_msgs.msg import String
|
|
||||||
from action_tutorials_interfaces.action import Fibonacci
|
|
||||||
|
|
||||||
|
|
||||||
loop = None
|
|
||||||
|
|
||||||
def get_event_loop():
|
|
||||||
global loop
|
|
||||||
return loop
|
|
||||||
|
|
||||||
|
|
||||||
async def default_handle_accepted_callback_async(goal_handle):
|
|
||||||
"""Execute the goal."""
|
|
||||||
await goal_handle.execute()
|
|
||||||
|
|
||||||
|
|
||||||
class ServerGoalHandleX(ServerGoalHandle):
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
super().__init__(*args, **kwargs)
|
|
||||||
|
|
||||||
async def execute(self, execute_callback=None):
|
|
||||||
# It's possible that there has been a request to cancel the goal prior to executing.
|
|
||||||
# In this case we want to avoid the illegal state transition to EXECUTING
|
|
||||||
# but still call the users execute callback to let them handle canceling the goal.
|
|
||||||
if not self.is_cancel_requested:
|
|
||||||
self._update_state(_rclpy.GoalEvent.EXECUTE)
|
|
||||||
await self._action_server.notify_execute_async(self, execute_callback)
|
|
||||||
|
|
||||||
|
|
||||||
class ActionServerX(ActionServer):
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
super().__init__(*args, **kwargs)
|
|
||||||
self.register_handle_accepted_callback(default_handle_accepted_callback_async)
|
|
||||||
|
|
||||||
async def _execute_goal_request(self, request_header_and_message):
|
|
||||||
request_header, goal_request = request_header_and_message
|
|
||||||
goal_uuid = goal_request.goal_id
|
|
||||||
goal_info = GoalInfo()
|
|
||||||
goal_info.goal_id = goal_uuid
|
|
||||||
|
|
||||||
self._node.get_logger().debug('New goal request with ID: {0}'.format(goal_uuid.uuid))
|
|
||||||
|
|
||||||
# Check if goal ID is already being tracked by this action server
|
|
||||||
with self._lock:
|
|
||||||
goal_id_exists = self._handle.goal_exists(goal_info)
|
|
||||||
|
|
||||||
accepted = False
|
|
||||||
if not goal_id_exists:
|
|
||||||
# Call user goal callback
|
|
||||||
response = await await_or_execute(self._goal_callback, goal_request.goal)
|
|
||||||
if not isinstance(response, GoalResponse):
|
|
||||||
self._node.get_logger().warning(
|
|
||||||
'Goal request callback did not return a GoalResponse type. Rejecting goal.')
|
|
||||||
else:
|
|
||||||
accepted = GoalResponse.ACCEPT == response
|
|
||||||
|
|
||||||
if accepted:
|
|
||||||
# Stamp time of acceptance
|
|
||||||
goal_info.stamp = self._node.get_clock().now().to_msg()
|
|
||||||
|
|
||||||
# Create a goal handle
|
|
||||||
try:
|
|
||||||
with self._lock:
|
|
||||||
goal_handle = ServerGoalHandleX(self, goal_info, goal_request.goal)
|
|
||||||
except RuntimeError as e:
|
|
||||||
self._node.get_logger().error(
|
|
||||||
'Failed to accept new goal with ID {0}: {1}'.format(goal_uuid.uuid, e))
|
|
||||||
accepted = False
|
|
||||||
else:
|
|
||||||
self._goal_handles[bytes(goal_uuid.uuid)] = goal_handle
|
|
||||||
|
|
||||||
# Send response
|
|
||||||
response_msg = self._action_type.Impl.SendGoalService.Response()
|
|
||||||
response_msg.accepted = accepted
|
|
||||||
response_msg.stamp = goal_info.stamp
|
|
||||||
self._handle.send_goal_response(request_header, response_msg)
|
|
||||||
|
|
||||||
if not accepted:
|
|
||||||
self._node.get_logger().debug('New goal rejected: {0}'.format(goal_uuid.uuid))
|
|
||||||
return
|
|
||||||
|
|
||||||
self._node.get_logger().debug('New goal accepted: {0}'.format(goal_uuid.uuid))
|
|
||||||
|
|
||||||
# Provide the user a reference to the goal handle
|
|
||||||
# await await_or_execute(self._handle_accepted_callback, goal_handle)
|
|
||||||
asyncio.create_task(self._handle_accepted_callback(goal_handle))
|
|
||||||
|
|
||||||
async def notify_execute_async(self, goal_handle, execute_callback):
|
|
||||||
# Use provided callback, defaulting to a previously registered callback
|
|
||||||
if execute_callback is None:
|
|
||||||
if self._execute_callback is None:
|
|
||||||
return
|
|
||||||
execute_callback = self._execute_callback
|
|
||||||
|
|
||||||
# Schedule user callback for execution
|
|
||||||
self._node.get_logger().info(f"{events.get_running_loop()}")
|
|
||||||
asyncio.create_task(self._execute_goal(execute_callback, goal_handle))
|
|
||||||
# loop = asyncio.new_event_loop()
|
|
||||||
# asyncio.set_event_loop(loop)
|
|
||||||
# task = loop.create_task(self._execute_goal(execute_callback, goal_handle))
|
|
||||||
# await task
|
|
||||||
|
|
||||||
|
|
||||||
class ActionClientX(ActionClient):
|
|
||||||
feedback_queue = asyncio.Queue()
|
|
||||||
|
|
||||||
async def feedback_cb(self, msg):
|
|
||||||
await self.feedback_queue.put(msg)
|
|
||||||
|
|
||||||
async def send_goal_async(self, goal_msg):
|
|
||||||
goal_future = super().send_goal_async(
|
|
||||||
goal_msg,
|
|
||||||
feedback_callback=self.feedback_cb
|
|
||||||
)
|
|
||||||
client_goal_handle = await asyncio.ensure_future(goal_future)
|
|
||||||
if not client_goal_handle.accepted:
|
|
||||||
raise Exception("Goal rejected.")
|
|
||||||
result_future = client_goal_handle.get_result_async()
|
|
||||||
while True:
|
|
||||||
feedback_future = asyncio.ensure_future(self.feedback_queue.get())
|
|
||||||
tasks = [result_future, feedback_future]
|
|
||||||
await asyncio.wait(tasks, return_when=asyncio.FIRST_COMPLETED)
|
|
||||||
if result_future.done():
|
|
||||||
result = result_future.result().result
|
|
||||||
yield (None, result)
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
feedback = feedback_future.result().feedback
|
|
||||||
yield (feedback, None)
|
|
||||||
|
|
||||||
|
|
||||||
async def main(node):
|
|
||||||
print('Node started.')
|
|
||||||
action_client = ActionClientX(node, Fibonacci, 'fibonacci')
|
|
||||||
goal_msg = Fibonacci.Goal()
|
|
||||||
goal_msg.order = 10
|
|
||||||
async for (feedback, result) in action_client.send_goal_async(goal_msg):
|
|
||||||
if feedback:
|
|
||||||
print(f'Feedback: {feedback}')
|
|
||||||
else:
|
|
||||||
print(f'Result: {result}')
|
|
||||||
print('Finished.')
|
|
||||||
|
|
||||||
|
|
||||||
async def ros_loop_node(node):
|
|
||||||
while rclpy.ok():
|
|
||||||
rclpy.spin_once(node, timeout_sec=0)
|
|
||||||
await asyncio.sleep(1e-4)
|
|
||||||
|
|
||||||
|
|
||||||
async def ros_loop(executor: Executor):
|
|
||||||
while rclpy.ok():
|
|
||||||
executor.spin_once(timeout_sec=0)
|
|
||||||
await asyncio.sleep(1e-4)
|
|
||||||
|
|
||||||
|
|
||||||
def run_event_loop():
|
|
||||||
global loop
|
|
||||||
loop = asyncio.new_event_loop()
|
|
||||||
asyncio.set_event_loop(loop)
|
|
||||||
loop.run_forever()
|
|
||||||
|
|
||||||
|
|
||||||
def run_event_loop_in_thread():
|
|
||||||
thread = threading.Thread(target=run_event_loop, args=())
|
|
||||||
thread.start()
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
rclpy.init()
|
|
||||||
node = rclpy.create_node('async_subscriber')
|
|
||||||
future = asyncio.wait([ros_loop(node), main()])
|
|
||||||
asyncio.get_event_loop().run_until_complete(future)
|
|
||||||
@@ -1,7 +1,11 @@
|
|||||||
import psutil
|
import psutil
|
||||||
import pywinauto
|
import pywinauto
|
||||||
from pywinauto_recorder import UIApplication
|
try:
|
||||||
from pywinauto_recorder.player import UIPath, click, focus_on_application, exists, find, get_wrapper_path
|
from pywinauto_recorder import UIApplication
|
||||||
|
from pywinauto_recorder.player import UIPath, click, focus_on_application, exists, find, get_wrapper_path
|
||||||
|
except ImportError:
|
||||||
|
print("未安装pywinauto_recorder,部分功能无法使用,安装时注意enum")
|
||||||
|
pass
|
||||||
from pywinauto.controls.uiawrapper import UIAWrapper
|
from pywinauto.controls.uiawrapper import UIAWrapper
|
||||||
from pywinauto.application import WindowSpecification
|
from pywinauto.application import WindowSpecification
|
||||||
from pywinauto import findbestmatch
|
from pywinauto import findbestmatch
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
<?xml-model href="http://download.ros.org/schema/package_format3.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?>
|
<?xml-model href="http://download.ros.org/schema/package_format3.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?>
|
||||||
<package format="3">
|
<package format="3">
|
||||||
<name>unilabos_msgs</name>
|
<name>unilabos_msgs</name>
|
||||||
<version>0.10.15</version>
|
<version>0.10.16</version>
|
||||||
<description>ROS2 Messages package for unilabos devices</description>
|
<description>ROS2 Messages package for unilabos devices</description>
|
||||||
<maintainer email="changjh@pku.edu.cn">Junhan Chang</maintainer>
|
<maintainer email="changjh@pku.edu.cn">Junhan Chang</maintainer>
|
||||||
<maintainer email="18435084+Xuwznln@users.noreply.github.com">Xuwznln</maintainer>
|
<maintainer email="18435084+Xuwznln@users.noreply.github.com">Xuwznln</maintainer>
|
||||||
|
|||||||
Reference in New Issue
Block a user