增加配置文件和launch文件

This commit is contained in:
NuoDaJia02
2026-02-01 11:09:49 +08:00
parent 04d0606034
commit d7ad3991e1
4 changed files with 154 additions and 40 deletions

33
config/ctrlgui.yaml Normal file
View File

@@ -0,0 +1,33 @@
ctrlgui_node:
ros__parameters:
collection_enabled: false
data_base_dir: "/home/demo/hivecore_robot_os1/data"
log_base_dir: "/home/demo/hivecore_robot_os1/logs"
queue_size: 10
action_server_wait_timeout_sec: 1.0
work_info_log_throttle_sec: 1.0
joint_state_log_throttle_sec: 1.0
work_info_log_every_n: 100
bt_xml_log_max_len: 100
ui_update_period_sec: 0.5
host: "0.0.0.0"
port: 8080
topic_led_cmd: "/led_cmd"
service_rebuild_now: "/cerebrum/rebuild_now"
topic_robot_work_info: "/robot_work_info"
action_execute_bt: "/execute_bt_action"
topic_joint_states: "/joint_states"
action_gripper0_cmd: "/gripper_cmd0"
action_gripper1_cmd: "/gripper_cmd1"
topic_gripper0_status: "/gripper0/status"
topic_gripper1_status: "/gripper1/status"
topic_arm_errors: "/arm_errors"
service_clear_arm_error: "clear_arm_error"
topic_top_color: "/camera/color/image_raw"
topic_top_depth: "/camera/depth/image_raw"
topic_left_color: "/camera1/camera1/color/image_raw"
topic_left_depth: "/camera1/camera1/depth/image_rect_raw"
topic_right_color: "/camera2/camera2/color/image_raw"
topic_right_depth: "/camera2/camera2/depth/image_rect_raw"

View File

@@ -36,55 +36,122 @@ from interfaces.msg import GripperStatus
class CtrlGuiNode(Node):
"""ROS2 node that publishes String messages to /led_cmd."""
def _declare_parameters(self) -> None:
self.declare_parameter('collection_enabled', False)
self.declare_parameter('data_base_dir', '/home/demo/hivecore_robot_os1/data')
self.declare_parameter('log_base_dir', '/home/demo/hivecore_robot_os1/logs')
self.declare_parameter('queue_size', 10)
self.declare_parameter('action_server_wait_timeout_sec', 1.0)
self.declare_parameter('work_info_log_throttle_sec', 1.0)
self.declare_parameter('joint_state_log_throttle_sec', 1.0)
self.declare_parameter('work_info_log_every_n', 100)
self.declare_parameter('bt_xml_log_max_len', 100)
self.declare_parameter('ui_update_period_sec', 0.5)
self.declare_parameter('host', '0.0.0.0')
self.declare_parameter('port', 8080)
# Topics/services/actions
self.declare_parameter('topic_led_cmd', '/led_cmd')
self.declare_parameter('service_rebuild_now', '/cerebrum/rebuild_now')
self.declare_parameter('topic_robot_work_info', '/robot_work_info')
self.declare_parameter('action_execute_bt', '/execute_bt_action')
self.declare_parameter('topic_joint_states', '/joint_states')
self.declare_parameter('action_gripper0_cmd', '/gripper_cmd0')
self.declare_parameter('action_gripper1_cmd', '/gripper_cmd1')
self.declare_parameter('topic_gripper0_status', '/gripper0/status')
self.declare_parameter('topic_gripper1_status', '/gripper1/status')
self.declare_parameter('topic_arm_errors', '/arm_errors')
self.declare_parameter('service_clear_arm_error', 'clear_arm_error')
self.declare_parameter('topic_top_color', '/camera/color/image_raw')
self.declare_parameter('topic_top_depth', '/camera/depth/image_raw')
self.declare_parameter('topic_left_color', '/camera1/camera1/color/image_raw')
self.declare_parameter('topic_left_depth', '/camera1/camera1/depth/image_rect_raw')
self.declare_parameter('topic_right_color', '/camera2/camera2/color/image_raw')
self.declare_parameter('topic_right_depth', '/camera2/camera2/depth/image_rect_raw')
def _load_parameters(self) -> None:
self.param_collection_enabled = bool(self.get_parameter('collection_enabled').value)
self.data_base_dir = str(self.get_parameter('data_base_dir').value)
self.log_base_dir = str(self.get_parameter('log_base_dir').value)
self.param_queue_size = int(self.get_parameter('queue_size').value)
self.param_action_server_wait_timeout_sec = float(self.get_parameter('action_server_wait_timeout_sec').value)
self.param_work_info_log_throttle_sec = float(self.get_parameter('work_info_log_throttle_sec').value)
self.param_joint_state_log_throttle_sec = float(self.get_parameter('joint_state_log_throttle_sec').value)
self.param_work_info_log_every_n = int(self.get_parameter('work_info_log_every_n').value)
self.param_bt_xml_log_max_len = int(self.get_parameter('bt_xml_log_max_len').value)
self.param_ui_update_period_sec = float(self.get_parameter('ui_update_period_sec').value)
self.param_host = str(self.get_parameter('host').value)
self.param_port = int(self.get_parameter('port').value)
self.topic_led_cmd = str(self.get_parameter('topic_led_cmd').value)
self.service_rebuild_now = str(self.get_parameter('service_rebuild_now').value)
self.topic_robot_work_info = str(self.get_parameter('topic_robot_work_info').value)
self.action_execute_bt = str(self.get_parameter('action_execute_bt').value)
self.topic_joint_states = str(self.get_parameter('topic_joint_states').value)
self.action_gripper0_cmd = str(self.get_parameter('action_gripper0_cmd').value)
self.action_gripper1_cmd = str(self.get_parameter('action_gripper1_cmd').value)
self.topic_gripper0_status = str(self.get_parameter('topic_gripper0_status').value)
self.topic_gripper1_status = str(self.get_parameter('topic_gripper1_status').value)
self.topic_arm_errors = str(self.get_parameter('topic_arm_errors').value)
self.service_clear_arm_error = str(self.get_parameter('service_clear_arm_error').value)
self.topic_top_color = str(self.get_parameter('topic_top_color').value)
self.topic_top_depth = str(self.get_parameter('topic_top_depth').value)
self.topic_left_color = str(self.get_parameter('topic_left_color').value)
self.topic_left_depth = str(self.get_parameter('topic_left_depth').value)
self.topic_right_color = str(self.get_parameter('topic_right_color').value)
self.topic_right_depth = str(self.get_parameter('topic_right_depth').value)
def __init__(self, node_name: str = 'ctrlgui_node'):
super().__init__(node_name)
self._declare_parameters()
self._load_parameters()
# Initialize counters for monitoring
self.messages_sent = 0
self.messages_received = 0
self.rebuild_requests = 0
self.start_time = datetime.datetime.now()
# Data collection enable flag (performance toggle)
self.collection_enabled = True
self.collection_enabled = self.param_collection_enabled
# Setup logging to file
self._setup_file_logging()
# publisher on /led_cmd
self.publisher_ = self.create_publisher(String, '/led_cmd', 10)
self.run_trigger_ = self.create_client(BtRebuild, '/cerebrum/rebuild_now')
self.publisher_ = self.create_publisher(String, self.topic_led_cmd, self.param_queue_size)
self.run_trigger_ = self.create_client(BtRebuild, self.service_rebuild_now)
# subscribe to /robot_work_info to display incoming messages in the UI
self.last_work_info: RobotWorkInfo = RobotWorkInfo()
self._sub_work_info = self.create_subscription(
RobotWorkInfo, '/robot_work_info', self._on_work_info, 10
RobotWorkInfo, self.topic_robot_work_info, self._on_work_info, self.param_queue_size
)
# bt action client
self.exe_bt_action_client_ = ActionClient(self, ExecuteBtAction, '/execute_bt_action')
self.exe_bt_action_client_ = ActionClient(self, ExecuteBtAction, self.action_execute_bt)
self.exe_bt_action_epoch = 0
self.joint_state_sub_ = self.create_subscription(
JointState, '/joint_states', self._on_joint_state, 10
JointState, self.topic_joint_states, self._on_joint_state, self.param_queue_size
)
self.gripper0_action_client_ = ActionClient(self, GripperCmd, '/gripper_cmd0')
self.gripper1_action_client_ = ActionClient(self, GripperCmd, '/gripper_cmd1')
self.gripper0_action_client_ = ActionClient(self, GripperCmd, self.action_gripper0_cmd)
self.gripper1_action_client_ = ActionClient(self, GripperCmd, self.action_gripper1_cmd)
# gripper status subscription
self.last_gripper_status = {0: GripperStatus(), 1: GripperStatus()}
self._sub_gripper0_status = self.create_subscription(
GripperStatus, '/gripper0/status', lambda msg: self._on_gripper_status(msg, 0), 10
GripperStatus, self.topic_gripper0_status, lambda msg: self._on_gripper_status(msg, 0), self.param_queue_size
)
self._sub_gripper1_status = self.create_subscription(
GripperStatus, '/gripper1/status', lambda msg: self._on_gripper_status(msg, 1), 10
GripperStatus, self.topic_gripper1_status, lambda msg: self._on_gripper_status(msg, 1), self.param_queue_size
)
# arm error subscription
self.last_arm_error = {0: None, 1: None}
self._sub_arm_error = self.create_subscription(
ArmError, '/arm_errors', self._on_arm_error, 10
ArmError, self.topic_arm_errors, self._on_arm_error, self.param_queue_size
)
# clear arm error service client
self.clear_arm_err_client_ = self.create_client(ClearArmError, 'clear_arm_error')
self.clear_arm_err_client_ = self.create_client(ClearArmError, self.service_clear_arm_error)
# Top Camera subscription
self.bridge = CvBridge()
@@ -122,6 +189,8 @@ class CtrlGuiNode(Node):
async def _wait_for_action_server(self, client: ActionClient, timeout_sec: float = 1.0) -> bool:
"""Asynchronously wait for an action server to be available."""
import asyncio
if timeout_sec is None:
timeout_sec = self.param_action_server_wait_timeout_sec
start = time.time()
while time.time() - start < timeout_sec:
if client.server_is_ready():
@@ -141,7 +210,7 @@ class CtrlGuiNode(Node):
self.file_logger.info(f"goal_msg: {goal_msg}")
# Non-blocking wait for server
if not await self._wait_for_action_server(self.exe_bt_action_client_, 1.0):
if not await self._wait_for_action_server(self.exe_bt_action_client_, None):
self.file_logger.error('BT Action Server not available')
ui.notify('BT Action Server not available', color='negative')
return
@@ -193,7 +262,7 @@ class CtrlGuiNode(Node):
client = self.gripper0_action_client_ if id == 0 else self.gripper1_action_client_
if not await self._wait_for_action_server(client, 1.0):
if not await self._wait_for_action_server(client, None):
ui.notify(f'Gripper {id} server not available', color='negative')
return
@@ -255,8 +324,7 @@ class CtrlGuiNode(Node):
date_str = now.strftime("%Y%m%d")
time_str = now.strftime("%H%M%S")
data_base_dir = "/home/demo/hivecore_robot_os1/data"
self.data_collection_dir = os.path.join(data_base_dir, date_str, time_str)
self.data_collection_dir = os.path.join(self.data_base_dir, date_str, time_str)
try:
os.makedirs(self.data_collection_dir, exist_ok=True)
os.makedirs(os.path.join(self.data_collection_dir, 'images'), exist_ok=True)
@@ -302,27 +370,27 @@ class CtrlGuiNode(Node):
def _create_camera_subscriptions(self) -> None:
if self._sub_top_image is None:
self._sub_top_image = self.create_subscription(
Image, '/camera/color/image_raw', self._on_top_image, 10
Image, self.topic_top_color, self._on_top_image, self.param_queue_size
)
if self._sub_top_depth is None:
self._sub_top_depth = self.create_subscription(
Image, '/camera/depth/image_raw', self._on_top_depth, 10
Image, self.topic_top_depth, self._on_top_depth, self.param_queue_size
)
if self._sub_left_image is None:
self._sub_left_image = self.create_subscription(
Image, '/camera1/camera1/color/image_raw', self._on_left_image, 10
Image, self.topic_left_color, self._on_left_image, self.param_queue_size
)
if self._sub_left_depth is None:
self._sub_left_depth = self.create_subscription(
Image, '/camera1/camera1/depth/image_rect_raw', self._on_left_depth, 10
Image, self.topic_left_depth, self._on_left_depth, self.param_queue_size
)
if self._sub_right_image is None:
self._sub_right_image = self.create_subscription(
Image, '/camera2/camera2/color/image_raw', self._on_right_image, 10
Image, self.topic_right_color, self._on_right_image, self.param_queue_size
)
if self._sub_right_depth is None:
self._sub_right_depth = self.create_subscription(
Image, '/camera2/camera2/depth/image_rect_raw', self._on_right_depth, 10
Image, self.topic_right_depth, self._on_right_depth, self.param_queue_size
)
def _destroy_camera_subscriptions(self) -> None:
@@ -375,7 +443,7 @@ class CtrlGuiNode(Node):
def _setup_file_logging(self):
"""Setup logging to a file with timestamp in filename."""
# Base logs directory
base_log_dir = "/home/demo/hivecore_robot_os1/logs"
base_log_dir = self.log_base_dir
now = datetime.datetime.now()
today_str = now.strftime("%Y%m%d")
today_dir = os.path.join(base_log_dir, today_str)
@@ -429,7 +497,7 @@ class CtrlGuiNode(Node):
current_time = time.time()
# Throttle logging to 1Hz
if current_time - self.last_work_info_log_time >= 1.0:
if current_time - self.last_work_info_log_time >= self.param_work_info_log_throttle_sec:
# Logging content for diagnostic purposes
action_strs = []
for action in msg.action:
@@ -437,7 +505,11 @@ class CtrlGuiNode(Node):
action_strs.append(f"{{skill: {action.skill}, action_name: {action.action_name}, params: {action.params}, execution: {action.execution}, state: {action.state}, stamp: {ts}}}")
# Truncate bt_xml in periodic logs to save CPU and log size
bt_xml_log = (msg.bt_xml[:100] + "...") if msg.bt_xml and len(msg.bt_xml) > 100 else str(msg.bt_xml)
bt_xml_log = (
(msg.bt_xml[:self.param_bt_xml_log_max_len] + "...")
if msg.bt_xml and len(msg.bt_xml) > self.param_bt_xml_log_max_len
else str(msg.bt_xml)
)
log_msg = (
f"Message content - msg_id: {str(msg.msg_id)}, working_state: {str(msg.working_state)}, "
@@ -477,7 +549,7 @@ class CtrlGuiNode(Node):
self.last_bt_status = current_bt_status
# Log summary periodically
if self.messages_received % 100 == 0:
if self.messages_received % self.param_work_info_log_every_n == 0:
self.file_logger.info(f'Processed {self.messages_received} messages from /robot_work_info')
def _log_session_summary(self, msg: RobotWorkInfo, end_status: str):
@@ -672,7 +744,7 @@ class CtrlGuiNode(Node):
# Throttle logging to 1Hz
current_time = time.time()
if current_time - self.last_joint_state_log_time >= 1.0:
if current_time - self.last_joint_state_log_time >= self.param_joint_state_log_throttle_sec:
self.file_logger.info(f"/joint_states positions: {str(self.last_joint_state.position)}")
self.last_joint_state_log_time = current_time
@@ -810,7 +882,10 @@ def build_ui(node: CtrlGuiNode) -> None:
else:
ui.notify('已启用数据采集与图像保存流程', color='positive')
collection_enable_btn = ui.button('禁用采集流程', on_click=toggle_collection_enabled)
collection_enable_btn = ui.button(
'禁用采集流程' if node.collection_enabled else '启用采集流程',
on_click=toggle_collection_enabled
)
# confirm dialog function
def show_confirm_dialog(message: str, confirm_callback) -> None:
@@ -1319,7 +1394,7 @@ def build_ui(node: CtrlGuiNode) -> None:
_set_label_if_changed(l_torque, f'gripper_{gid}_torque', 'Torque: N/A')
_set_label_if_changed(l_state, f'gripper_{gid}_state', 'State: N/A')
ui.timer(0.5, update_work_info)
ui.timer(node.param_ui_update_period_sec, update_work_info)
def main() -> None:
"""Start rclpy, the ROS2 node in a background thread, and run the NiceGUI app.
@@ -1390,11 +1465,8 @@ def main() -> None:
atexit.register(shutdown)
# Start NiceGUI honoring HOST/PORT environment variables (works with ros2 launch env)
host = os.getenv('HOST', '0.0.0.0')
try:
port = int(os.getenv('PORT', '8080'))
except ValueError:
port = 8080
host = node.param_host
port = node.param_port
print(f'Starting NiceGUI on {host}:{port}...')
ui.run(title='HiveCore Robot', host=host, port=port, reload=False, uvicorn_logging_level='info')

View File

@@ -2,6 +2,8 @@ from launch import LaunchDescription
from launch.actions import DeclareLaunchArgument
from launch.substitutions import LaunchConfiguration
from launch_ros.actions import Node
from ament_index_python.packages import get_package_share_directory
import os
def generate_launch_description() -> LaunchDescription:
@@ -15,8 +17,12 @@ def generate_launch_description() -> LaunchDescription:
port_arg = DeclareLaunchArgument(
'port', default_value='8080',
description='TCP port for NiceGUI')
params_file_arg = DeclareLaunchArgument(
'params_file',
default_value=os.path.join(get_package_share_directory('ctrlgui'), 'config', 'ctrlgui.yaml'),
description='Path to the ctrlgui parameter file')
ld = LaunchDescription([node_name_arg, host_arg, port_arg])
ld = LaunchDescription([node_name_arg, host_arg, port_arg, params_file_arg])
ctrlgui_node = Node(
package='ctrlgui',
@@ -24,11 +30,13 @@ def generate_launch_description() -> LaunchDescription:
name=LaunchConfiguration('node_name'),
output='screen',
emulate_tty=True,
parameters=[],
env={
'HOST': LaunchConfiguration('host'),
'PORT': LaunchConfiguration('port'),
},
parameters=[
LaunchConfiguration('params_file'),
{
'host': LaunchConfiguration('host'),
'port': LaunchConfiguration('port'),
},
],
)
ld.add_action(ctrlgui_node)

View File

@@ -17,6 +17,7 @@ setup(
['resource/' + package_name]),
('share/' + package_name, ['package.xml']),
('share/' + package_name + '/launch', ['launch/ctrlgui.launch.py']),
('share/' + package_name + '/config', ['config/ctrlgui.yaml']),
# Install top-level static assets into share for runtime serving
('share/' + package_name + '/static', [
*glob(os.path.join('static', '*'))