|
| 1 | +""" |
| 2 | + Jason Hughes |
| 3 | + January 2025 |
| 4 | +
|
| 5 | + Launch the factor graph node |
| 6 | +""" |
| 7 | + |
| 8 | +import os |
| 9 | +import launch |
| 10 | + |
| 11 | +import ament_index_python.packages |
| 12 | + |
| 13 | +from launch.actions import DeclareLaunchArgument as LaunchArg |
| 14 | +from launch.actions import OpaqueFunction |
| 15 | +from launch.substitutions import LaunchConfiguration as LaunchConfig |
| 16 | +from launch.substitutions import PathJoinSubstitution as PJoin |
| 17 | +from launch_ros.actions import ComposableNodeContainer, Node |
| 18 | +from launch_ros.descriptions import ComposableNode |
| 19 | +from launch_ros.substitutions import FindPackageShare |
| 20 | +from ament_index_python.packages import get_package_share_directory |
| 21 | + |
| 22 | + |
| 23 | +import yaml |
| 24 | + |
| 25 | + |
| 26 | +# --------- master_example.launch.py |
| 27 | + |
| 28 | + |
| 29 | +camera_list = { |
| 30 | + 'cam0': '23540201', |
| 31 | +} |
| 32 | + |
| 33 | +serial = '23540201' |
| 34 | +camera_type = 'blackfly_s' |
| 35 | +parameter_file = PJoin( |
| 36 | + [FindPackageShare('spinnaker_camera_driver'), 'config', camera_type + '.yaml'] |
| 37 | + ) |
| 38 | + |
| 39 | +exposure_controller_parameters = { |
| 40 | + 'type': 'master', |
| 41 | + 'brightness_target': 120, # from 0..255 |
| 42 | + 'brightness_tolerance': 20, # when to update exposure/gain |
| 43 | + # watch that max_exposure_time is short enough |
| 44 | + # to support the trigger frame rate! |
| 45 | + 'max_exposure_time': 15000, # usec |
| 46 | + 'min_exposure_time': 5000, # usec |
| 47 | + 'max_gain': 29.9, |
| 48 | + 'gain_priority': False, |
| 49 | +} |
| 50 | + |
| 51 | +camera_params = { |
| 52 | + 'debug': False, |
| 53 | + 'quiet': True, |
| 54 | + 'buffer_queue_size': 1, |
| 55 | + 'compute_brightness': True, |
| 56 | + 'exposure_auto': 'Continuous', |
| 57 | + 'exposure_time': 10000, # not used under auto exposure |
| 58 | + 'trigger_mode': 'Off', |
| 59 | + 'frame_rate_auto': 'Off', |
| 60 | + 'frame_rate_enable': True, |
| 61 | + 'gain_auto': 'Continuous', |
| 62 | + 'trigger_source': 'Software', |
| 63 | + 'trigger_selector': 'FrameStart', |
| 64 | + 'trigger_overlap': 'ReadOut', |
| 65 | + 'trigger_activation': 'RisingEdge', |
| 66 | + 'balance_white_auto': 'Continuous', |
| 67 | + # You must enable chunk mode and chunks: frame_id, exposure_time, and gain |
| 68 | + 'chunk_mode_active': True, |
| 69 | + 'chunk_selector_frame_id': 'FrameID', |
| 70 | + 'chunk_enable_frame_id': True, |
| 71 | + 'chunk_selector_exposure_time': 'ExposureTime', |
| 72 | + 'chunk_enable_exposure_time': True, |
| 73 | + 'chunk_selector_gain': 'Gain', |
| 74 | + 'chunk_enable_gain': True, |
| 75 | + # The Timestamp is not used at the moment |
| 76 | + 'chunk_selector_timestamp': 'Timestamp', |
| 77 | + 'chunk_enable_timestamp': True, |
| 78 | + 'frame_rate': 5 |
| 79 | +} |
| 80 | + |
| 81 | + |
| 82 | +def make_parameters(context): |
| 83 | + """Launch synchronized camera driver node.""" |
| 84 | + pd = LaunchConfig('camera_parameter_directory') |
| 85 | + calib_url = 'file://' + LaunchConfig('calibration_directory').perform(context) + '/' |
| 86 | + |
| 87 | + exp_ctrl_names = [cam + '.exposure_controller' for cam in camera_list.keys()] |
| 88 | + driver_parameters = { |
| 89 | + 'cameras': list(camera_list.keys()), |
| 90 | + 'exposure_controllers': exp_ctrl_names, |
| 91 | + 'ffmpeg_image_transport.encoding': 'hevc_nvenc', # only for ffmpeg image transport |
| 92 | + } |
| 93 | + # generate identical exposure controller parameters for all cameras |
| 94 | + for exp in exp_ctrl_names: |
| 95 | + driver_parameters.update( |
| 96 | + {exp + '.' + k: v for k, v in exposure_controller_parameters.items()} |
| 97 | + ) |
| 98 | + |
| 99 | + # generate camera parameters |
| 100 | + cam_parameters['parameter_file'] = PJoin([pd, 'blackfly_s.yaml']) |
| 101 | + for cam, serial in camera_list.items(): |
| 102 | + cam_params = {cam + '.' + k: v for k, v in cam_parameters.items()} |
| 103 | + cam_params[cam + '.serial_number'] = serial |
| 104 | + cam_params[cam + '.camerainfo_url'] = calib_url + serial + '.yaml' |
| 105 | + cam_params[cam + '.frame_id'] = cam |
| 106 | + driver_parameters.update(cam_params) # insert into main parameter list |
| 107 | + # link the camera to its exposure controller. Each camera has its own controller |
| 108 | + driver_parameters.update({cam + '.exposure_controller_name': cam + '.exposure_controller'}) |
| 109 | + return driver_parameters |
| 110 | + |
| 111 | + |
| 112 | +# ---------------------------------- |
| 113 | + |
| 114 | +def launch_setup(context, *args, **kwargs): |
| 115 | + """Create composable node.""" |
| 116 | + # For the camera |
| 117 | + #cam_name = LaunchConfig("camera_name") |
| 118 | + #cam_str = cam_name.perform(context) |
| 119 | + |
| 120 | + # For the GPS |
| 121 | + config_directory = os.path.join( |
| 122 | + ament_index_python.packages.get_package_share_directory('ublox_gps'), |
| 123 | + 'config') |
| 124 | + param_config = os.path.join(config_directory, 'zed_f9p.yaml') |
| 125 | + with open(param_config, 'r') as f: |
| 126 | + params = yaml.safe_load(f)['ublox_gps_node']['ros__parameters'] |
| 127 | + |
| 128 | + # For EC |
| 129 | + # bias_config = os.path.join( |
| 130 | + # ament_index_python.packages.get_package_share_directory('high_altitude_ec'), |
| 131 | + # 'config/silky_ev_all_zero.bias') |
| 132 | + bias_config = "/home/dcist/fclad/ROS/high_altitude_env/src/high_altitude_ec/config/silkyHD_all_zero.bias" |
| 133 | + # Declare launch arguments |
| 134 | + |
| 135 | + # Find package share directory |
| 136 | + glider_share = FindPackageShare('glider') |
| 137 | + glider_share_dir = get_package_share_directory('glider') |
| 138 | + |
| 139 | + # Path to parameter files |
| 140 | + ros_params_file = PJoin([ |
| 141 | + glider_share, |
| 142 | + 'config', |
| 143 | + 'ros-params.yaml' |
| 144 | + ]) |
| 145 | + |
| 146 | + graph_params_file = PJoin([ |
| 147 | + glider_share, |
| 148 | + 'config', |
| 149 | + 'vectornav-vn100t.yaml' |
| 150 | + ]) |
| 151 | + |
| 152 | + container = ComposableNodeContainer( |
| 153 | + name="high_altitude_ec_container", |
| 154 | + namespace="", |
| 155 | + package="rclcpp_components", |
| 156 | + executable="component_container", |
| 157 | + # prefix=['xterm -e gdb -ex run --args'], |
| 158 | + composable_node_descriptions=[ |
| 159 | + # Rosbag2 recorder |
| 160 | + ComposableNode( |
| 161 | + package='rosbag2_composable_recorder', |
| 162 | + plugin='rosbag2_composable_recorder::ComposableRecorder', |
| 163 | + name="recorder", |
| 164 | + parameters=[{'topics': [ |
| 165 | + "/ublox_gps_node/fix", |
| 166 | + "/odom", |
| 167 | + "/vectornav/imu", |
| 168 | + "/vectornav/magnetic", |
| 169 | + "/cam_driver/image_raw" |
| 170 | + ], |
| 171 | + 'storage_id': 'mcap', |
| 172 | + 'record_all': False, |
| 173 | + 'disable_discovery': False, |
| 174 | + 'serialization_format': 'cdr', |
| 175 | + 'start_recording_immediately': False, |
| 176 | + "bag_name": LaunchConfig("bag"), |
| 177 | + "bag_prefix": LaunchConfig("bag_prefix")}], |
| 178 | + remappings=[], |
| 179 | + extra_arguments=[{'use_intra_process_comms': True}], |
| 180 | + ), |
| 181 | + |
| 182 | + # Vectornav Raw |
| 183 | + ComposableNode( |
| 184 | + package='vectornav', |
| 185 | + plugin='vectornav::Vectornav', |
| 186 | + name='vectornav', |
| 187 | + parameters=[PJoin( |
| 188 | + [FindPackageShare('vectornav'), |
| 189 | + 'config', 'vectornav_composable.yaml'])], |
| 190 | + remappings=[], |
| 191 | + extra_arguments=[{'use_intra_process_comms': True}]), |
| 192 | + |
| 193 | + # FLIR camera |
| 194 | + ComposableNode( |
| 195 | + package='spinnaker_camera_driver', |
| 196 | + plugin='spinnaker_camera_driver::CameraDriver', |
| 197 | + name="cam_driver", |
| 198 | + parameters=[camera_params, {'parameter_file': parameter_file, 'serial_number': serial}], |
| 199 | + remappings=[ |
| 200 | + ('~/control', '/exposure_control/control'), |
| 201 | + ], |
| 202 | + extra_arguments=[{'use_intra_process_comms': True}], |
| 203 | + ), |
| 204 | + # Ublox GPS |
| 205 | + ComposableNode( |
| 206 | + package='ublox_gps', |
| 207 | + plugin='ublox_node::UbloxNode', |
| 208 | + name='ublox_gps_node', |
| 209 | + parameters=[params], |
| 210 | + remappings=[("/aidalm", "/ublox_raw/aidalm"), |
| 211 | + ("/timtm2", "/ublox_raw/timtm2"), |
| 212 | + ("/rtcm", "/ublox_raw/rtcm"), |
| 213 | + ("/nmea", "/ublox_raw/nmea"), |
| 214 | + ("/navclock", "/ublox_raw/navclock"), |
| 215 | + ("/navcov", "/ublox_raw/navcov"), |
| 216 | + ("/navheading", "/ublox_raw/navheading"), |
| 217 | + ("/navrelposned", "/ublox_raw/navrelposned"), |
| 218 | + ("/navstate", "/ublox_raw/navstate"), |
| 219 | + ("/navsvin", "/ublox_raw/navsvin"), |
| 220 | + ("/navstatus", "/ublox_raw/navstatus"), |
| 221 | + ("/aideph", "/ublox_raw/aideph"), |
| 222 | + ("/diagnostics", "/ublox_raw/diagnostics"), |
| 223 | + ("/monhw", "/ublox_raw/monhw"), |
| 224 | + ("/navsin", "/ublox_raw/nmea"), |
| 225 | + ("/rtcm", "/ublox_raw/rtcm"), |
| 226 | + ("/rxmrtcm", "/ublox_raw/rxmrtcm")], |
| 227 | + extra_arguments=[{'use_intra_process_comms': True}], |
| 228 | + ), |
| 229 | + ComposableNode( |
| 230 | + package='glider', |
| 231 | + plugin='GliderROS::GliderNode', |
| 232 | + name='glider_node', |
| 233 | + parameters=[ |
| 234 | + ros_params_file, |
| 235 | + {'path': graph_params_file, |
| 236 | + 'use_sim_time': False, |
| 237 | + 'use_odom': False} |
| 238 | + ], |
| 239 | + remappings=[("/gps", "/ublox_gps_node/fix"), ("/imu", "/vectornav/imu")], |
| 240 | + ), |
| 241 | + |
| 242 | + ComposableNode( |
| 243 | + package='vectornav', |
| 244 | + plugin='vectornav::VnSensorMsgs', |
| 245 | + name='vn_sensor_msgs', |
| 246 | + parameters=[PJoin( |
| 247 | + [FindPackageShare('vectornav'), |
| 248 | + 'config', 'vn_sensor_msgs_composable.yaml'])], |
| 249 | + remappings=[], |
| 250 | + extra_arguments=[{'use_intra_process_comms': True}] |
| 251 | + ), |
| 252 | + ], |
| 253 | + output="screen", |
| 254 | + ) |
| 255 | + return [container] |
| 256 | + |
| 257 | + |
| 258 | + |
| 259 | + |
| 260 | +def generate_launch_description(): |
| 261 | + """Create composable node by calling opaque function.""" |
| 262 | + return launch.LaunchDescription( |
| 263 | + [ |
| 264 | + LaunchArg("bag", default_value=[""], description="name of output bag"), |
| 265 | + LaunchArg("bag_prefix", default_value=["/home/dcist/data/symbiote_"], description="prefix of output bag"), |
| 266 | + # FLIR camera |
| 267 | + LaunchArg( |
| 268 | + 'camera_parameter_directory', |
| 269 | + default_value=PJoin([FindPackageShare('spinnaker_camera_driver'), 'config']), |
| 270 | + description='root directory for camera parameter definitions', |
| 271 | + ), |
| 272 | + LaunchArg( |
| 273 | + 'calibration_directory', |
| 274 | + default_value=['camera_calibrations'], |
| 275 | + description='root directory for camera calibration files', |
| 276 | + ), |
| 277 | + # This is for the composed nodes |
| 278 | + OpaqueFunction(function=launch_setup), |
| 279 | + #Node( |
| 280 | + # package="sf000_driver", |
| 281 | + # executable="reader.py", |
| 282 | + # name="reader", |
| 283 | + # remappings=[("/range", "/sf000/range")], |
| 284 | + #) |
| 285 | + ] |
| 286 | + ) |
0 commit comments