feat: 接入番茄采摘ACT采集启动项

This commit is contained in:
2026-08-10 18:24:02 +08:00
parent 6008e34b5d
commit 6982620041
3 changed files with 98 additions and 0 deletions
+33
View File
@@ -0,0 +1,33 @@
act_episode_recorder:
ros__parameters:
output_root: /home/robot/ACT_Data
task_name: tomato_pick
control_sample_topic: /xr_rm/right_rm75/act_control_sample
right_controller_topic: /xr/right_controller
left_controller_topic: /xr/left_controller
status_topic: /act/recording_status
cam_high_serial: "234222303366"
cam_high_model: D455
cam_right_wrist_serial: "412622272532"
cam_right_wrist_model: D405
image_width: 640
image_height: 480
camera_fps: 30
camera_warmup_sec: 5.0
control_rate_hz: 90.0
sample_rate_hz: 30.0
min_samples: 60
max_samples: 1800
min_control_hz: 27.0
max_control_gap_ms: 100.0
min_camera_fps: 27.0
max_drop_ratio: 0.01
max_feedback_age_ms: 50.0
max_camera_age_ms: 50.0
max_camera_skew_ms: 50.0
min_free_space_gib: 4.0
y_hold_sec: 1.0
gripper_completion_timeout_sec: 3.0
writer_queue_size: 8
+31
View File
@@ -80,6 +80,29 @@ def _validate_mujoco_mode(arm: str, use_mujoco: bool) -> None:
raise ValueError("use_mujoco:=true requires arm:=both") raise ValueError("use_mujoco:=true requires arm:=both")
def _validate_act_mode(
arm: str,
use_mock: bool,
record_act: bool,
) -> None:
if record_act and (arm != "right" or use_mock):
raise ValueError(
"record_act:=true requires arm:=right use_mock:=false"
)
def _act_recorder_node() -> Node:
"""创建独立 ACT 数据采集节点;退出时不终止遥操作。"""
return Node(
package="xr_rm_teleop",
executable="act_episode_recorder",
name="act_episode_recorder",
output="screen",
prefix=[XR_PYTHON],
parameters=[_config_file("act_tomato_pick.yaml")],
)
def _single_arm_node( def _single_arm_node(
arm: str, arm: str,
use_mock: bool, use_mock: bool,
@@ -164,10 +187,14 @@ def _launch_setup(context, *args, **kwargs):
use_mujoco = _as_bool( use_mujoco = _as_bool(
LaunchConfiguration("use_mujoco").perform(context) LaunchConfiguration("use_mujoco").perform(context)
) )
record_act = _as_bool(
LaunchConfiguration("record_act").perform(context)
)
if arm not in ("left", "right", "both"): if arm not in ("left", "right", "both"):
raise ValueError("arm must be one of: left, right, both") raise ValueError("arm must be one of: left, right, both")
_validate_mujoco_mode(arm, use_mujoco) _validate_mujoco_mode(arm, use_mujoco)
_validate_act_mode(arm, use_mock, record_act)
nodes = [_udp_receiver_node()] nodes = [_udp_receiver_node()]
if arm == "both": if arm == "both":
@@ -176,6 +203,8 @@ def _launch_setup(context, *args, **kwargs):
nodes.append(_single_arm_node(arm, use_mock)) nodes.append(_single_arm_node(arm, use_mock))
if use_mujoco: if use_mujoco:
nodes.append(_mujoco_node()) nodes.append(_mujoco_node())
if record_act:
nodes.append(_act_recorder_node())
return nodes return nodes
@@ -187,6 +216,8 @@ def generate_launch_description() -> LaunchDescription:
DeclareLaunchArgument("use_mock", default_value="true"), DeclareLaunchArgument("use_mock", default_value="true"),
# true 时额外启动只读 MuJoCo 双臂显示,默认不改变现有启动行为。 # true 时额外启动只读 MuJoCo 双臂显示,默认不改变现有启动行为。
DeclareLaunchArgument("use_mujoco", default_value="false"), DeclareLaunchArgument("use_mujoco", default_value="false"),
# true 时只允许右臂真机,并启动独立 ACT 数据采集节点。
DeclareLaunchArgument("record_act", default_value="false"),
# UDP 监听参数,需要与 PICO 端或 sample_udp_sender 保持一致。 # UDP 监听参数,需要与 PICO 端或 sample_udp_sender 保持一致。
DeclareLaunchArgument("udp_host", default_value="0.0.0.0"), DeclareLaunchArgument("udp_host", default_value="0.0.0.0"),
DeclareLaunchArgument("udp_port", default_value="15000"), DeclareLaunchArgument("udp_port", default_value="15000"),
@@ -41,3 +41,37 @@ def test_udp_receiver_exit_shuts_down_launch() -> None:
receiver = arm_debug_launch._udp_receiver_node() receiver = arm_debug_launch._udp_receiver_node()
assert isinstance(receiver._ExecuteLocal__on_exit, Shutdown) assert isinstance(receiver._ExecuteLocal__on_exit, Shutdown)
def test_act_recording_is_disabled_by_default() -> None:
description = arm_debug_launch.generate_launch_description()
arguments = {
entity.name: entity
for entity in description.entities
if isinstance(entity, DeclareLaunchArgument)
}
assert perform_substitutions(
LaunchContext(),
arguments["record_act"].default_value,
) == "false"
@pytest.mark.parametrize(
("arm", "use_mock"),
[("left", False), ("both", False), ("right", True)],
)
def test_act_recording_rejects_unsupported_modes(arm, use_mock) -> None:
with pytest.raises(ValueError, match="arm:=right use_mock:=false"):
arm_debug_launch._validate_act_mode(arm, use_mock, True)
def test_act_recording_accepts_right_real_mode() -> None:
arm_debug_launch._validate_act_mode("right", False, True)
arm_debug_launch._validate_act_mode("both", True, False)
def test_act_recorder_exit_does_not_shutdown_teleoperation() -> None:
recorder = arm_debug_launch._act_recorder_node()
assert recorder._ExecuteLocal__on_exit is None