From 559f60dfd86b632be3ccb5a183dd978fdb79984b Mon Sep 17 00:00:00 2001 From: Grant Date: Thu, 7 May 2026 08:26:26 +0000 Subject: [PATCH] feat(bringup): add foxglove_bridge and MCAP recording --- .../launch/foxglove_mcap.launch.py | 74 +++++++++++++++++++ src/rov_bringup/launch/rov_full.launch.py | 7 ++ src/rov_bringup/package.xml | 1 + 3 files changed, 82 insertions(+) create mode 100644 src/rov_bringup/launch/foxglove_mcap.launch.py diff --git a/src/rov_bringup/launch/foxglove_mcap.launch.py b/src/rov_bringup/launch/foxglove_mcap.launch.py new file mode 100644 index 0000000..306ead2 --- /dev/null +++ b/src/rov_bringup/launch/foxglove_mcap.launch.py @@ -0,0 +1,74 @@ +""" +foxglove_mcap.launch.py +Launches foxglove_bridge (WebSocket :8765) and MCAP bag recording. +Included automatically by rov_full.launch.py. + +Standalone: + ros2 launch rov_bringup foxglove_mcap.launch.py + ros2 launch rov_bringup foxglove_mcap.launch.py record:=false + +Foxglove connection from Edge PC: + Foxglove Studio -> Open Connection -> Foxglove WebSocket -> ws://rov-brain.local:8765 + +Storage: + Bags written to /data/bags/ on NVMe. Create before first launch: + sudo mkdir -p /data/bags && sudo chown ubuntu:ubuntu /data/bags +""" +from launch import LaunchDescription +from launch.actions import DeclareLaunchArgument, ExecuteProcess, LogInfo +from launch.conditions import IfCondition +from launch.substitutions import LaunchConfiguration +from launch_ros.actions import Node + + +def generate_launch_description(): + + bag_output_arg = DeclareLaunchArgument( + 'bag_output_dir', + default_value='/data/bags/dive', + description='Base path for MCAP bag. Timestamp appended automatically.', + ) + + record_arg = DeclareLaunchArgument( + 'record', + default_value='true', + description='Set false to run foxglove_bridge without recording.', + ) + + # Exposes all ROS2 topics over WebSocket + # Edge PC connects via Foxglove Studio -> ws://rov-brain.local:8765 + foxglove_bridge_node = Node( + package='foxglove_bridge', + executable='foxglove_bridge', + name='foxglove_bridge', + parameters=[{ + 'port': 8765, + 'address': '0.0.0.0', # all interfaces — required for tether access + 'tls': False, + 'send_buffer_limit': 10000000, # 10MB — headroom for image topics + 'max_update_ms': 100, # 10Hz cap — prevents WebSocket flooding + }], + output='screen', + ) + + # Records all topics to NVMe in MCAP format (Foxglove native) + # Output dir is auto-timestamped, e.g.: /data/bags/dive_2026_05_07-14_22_05/ + mcap_recorder = ExecuteProcess( + cmd=[ + 'ros2', 'bag', 'record', + '--all', + '--storage', 'mcap', + '--output', LaunchConfiguration('bag_output_dir'), + '--max-bag-size', '0', # no size limit — one bag per dive + ], + output='screen', + condition=IfCondition(LaunchConfiguration('record')), + ) + + return LaunchDescription([ + bag_output_arg, + record_arg, + LogInfo(msg='foxglove_bridge: connect Foxglove Studio to ws://:8765'), + foxglove_bridge_node, + mcap_recorder, + ]) diff --git a/src/rov_bringup/launch/rov_full.launch.py b/src/rov_bringup/launch/rov_full.launch.py index 4ce5d8a..0f5e772 100644 --- a/src/rov_bringup/launch/rov_full.launch.py +++ b/src/rov_bringup/launch/rov_full.launch.py @@ -79,4 +79,11 @@ def generate_launch_description(): os.path.join(mission_pkg, 'launch', 'mission.launch.py') ), ), + + # Foxglove bridge + MCAP recording (Prong 2 + 3) + IncludeLaunchDescription( + PythonLaunchDescriptionSource( + os.path.join(bringup_pkg, 'launch', 'foxglove_mcap.launch.py') + ), + ), ]) diff --git a/src/rov_bringup/package.xml b/src/rov_bringup/package.xml index d983ca1..a31a3c3 100644 --- a/src/rov_bringup/package.xml +++ b/src/rov_bringup/package.xml @@ -14,4 +14,5 @@ ament_cmake + foxglove_bridge