""" rosbridge.launch.py — rosbridge WebSocket server + compressed image transport Starts two things: 1. rosbridge_websocket — WebSocket server on port 9090 (roslibjs-compatible) 2. image_transport/republish × 4 — compresses each CSI camera's raw stream /camera//image_raw → /camera//image_raw/compressed Prerequisites (already installed in saltybot/ros2-humble:jetson-orin): apt install ros-humble-rosbridge-server apt install ros-humble-image-transport-plugins # supplies compressed plugin Browser connection: var ros = new ROSLIB.Ros({ url: 'ws://:9090' }); Topic whitelist is configured in config/rosbridge_params.yaml. Bandwidth tips: - Subscribe to /camera//image_raw/compressed, not raw images. - Use throttle_rate in ROSLIB.Topic to cap camera update rate: new ROSLIB.Topic({ ... messageType: 'sensor_msgs/CompressedImage', throttle_rate: 200 }) // max 5 fps per camera - Subscribe to /map with throttle_rate: 5000 (0.2 Hz is enough for display). Usage: ros2 launch saltybot_bringup rosbridge.launch.py Verify: ros2 topic echo /rosbridge_websocket/status # In browser console: # var ros = new ROSLIB.Ros({ url: 'ws://jetson.local:9090' }); # ros.on('connection', () => console.log('connected')); """ import os from launch import LaunchDescription from launch_ros.actions import Node from ament_index_python.packages import get_package_share_directory # Camera names matching saltybot_cameras/launch/csi_cameras.launch.py # Topics: /camera//image_raw (published by v4l2_camera_node) _CAMERAS = ['front', 'left', 'rear', 'right'] # JPEG quality for compressed output (0–100). # 75 = good quality/size trade-off at 640×480: ~15–25 KB/frame. # Lower to 50 for tighter bandwidth budgets; raise to 90 for inspection use. _JPEG_QUALITY = 75 def generate_launch_description(): pkg_share = get_package_share_directory('saltybot_bringup') params_file = os.path.join(pkg_share, 'config', 'rosbridge_params.yaml') # ── rosbridge WebSocket server ──────────────────────────────────────────── rosbridge = Node( package='rosbridge_server', executable='rosbridge_websocket', name='rosbridge_websocket', parameters=[params_file], output='screen', ) # ── Compressed image republishers ───────────────────────────────────────── # image_transport/republish subscribes to raw sensor_msgs/Image and # re-publishes as sensor_msgs/CompressedImage using the compressed plugin. # # Node arguments: ['raw', 'compressed'] # 'raw' — input transport type # 'compressed' — output transport type # # Topic remappings: # in → /camera//image_raw # out/compressed → /camera//image_raw/compressed # # Parameter jpeg_quality controls JPEG encoder quality for the # compressed publisher. The full parameter path in ROS2 is: # //compressed/jpeg_quality republishers = [ Node( package='image_transport', executable='republish', name=f'compress_{name}', arguments=['raw', 'compressed'], remappings=[ ('in', f'/camera/{name}/image_raw'), ('out/compressed', f'/camera/{name}/image_raw/compressed'), ], parameters=[{ 'compressed.jpeg_quality': _JPEG_QUALITY, }], output='screen', ) for name in _CAMERAS ] # ── D435i colour republisher (Issue #177) ──────────────────────────────── d435i_color = Node( package='image_transport', executable='republish', name='compress_d435i_color', arguments=['raw', 'compressed'], remappings=[ ('in', '/camera/color/image_raw'), ('out/compressed', '/camera/color/image_raw/compressed'), ], parameters=[{'compressed.jpeg_quality': _JPEG_QUALITY}], output='screen', ) # ── D435i depth republisher (Issue #177) ───────────────────────────────── # Depth stream as compressedDepth (PNG16) — preserves uint16 depth values. # Browser displays as greyscale PNG (darker = closer). d435i_depth = Node( package='image_transport', executable='republish', name='compress_d435i_depth', arguments=['raw', 'compressedDepth'], remappings=[ ('in', '/camera/depth/image_rect_raw'), ('out/compressedDepth', '/camera/depth/image_rect_raw/compressed'), ], output='screen', ) return LaunchDescription([rosbridge] + republishers + [d435i_color, d435i_depth])