Launch PX4 via ROS2 python launch file

Hello,

I would like to know if it’s possible to launch PX4 via a ROS2 python launch file.
I have found that we can do this in ROS1 via the launch files in the folder “PX4-Autopilot/launch” so I guess that would be possible with ROS2.
I have tried and successfully installed the " RTPS/ROS2 Interface: PX4-FastRTPS Bridge". The two packages “px4_ros_com” and “px4_msgs” are available.
But I don’t know what to do with that, I’m quite lost and i spend quite a lot of time searching and trying things.
I have the latest version of PX4 and I use ROS2 Foxy.

Any help would be very appreciate.

1 Like

Yes it is possible but we have not made that available upstream yet - it’s just a matter of understanding of ROS2 launch files work and you can build your own. Otherwise, you will have to wait until me and someone else has the bandwidth for that.
Also using px4_ros_com doesn’t mean you can’t run the simulation as you normally do without starting it with launch files - you can use make px4_sitl_rtps gazebo, which will start the simulation, and then in the pxh shell, issue the micrortps_client start -t UDP to start the client side of the bridge.

it’s just a matter of understanding of ROS2 launch files work and you can build your own

First of all thanks for answering me, and yes and this is the part where I struggle. I tried to convert and adapt the posix_sitl.launch file.
To begin I used https://github.com/aws-robotics/ros2-launch-file-migrator which allows me to convert a ROS .xml launch file into a ROS2 .py launch file.

But this errors occurs :
launch.invalid_launch_file_error.InvalidLaunchFileError: Caught exception when trying to load file of format [py]: "package 'mavlink_sitl_gazebo' not found, searching: ['/opt/ros/foxy']

It just can’t find the mavlink_sitl_gazebo package in /opt/ros/foxy when it try to find the empty.world file. It’s not a big error and I can “bypass” it by just not providing a world file.

But I have the same issue when I want to launch the px4 package as a ROS2 Node (I don’t know if “launch a package as a ROS2 Node” is a correct term), I think this is where the problem stands (in my comprehension of ROS2 launch file). Here is the code where I launch it (I only put what I think is necessary) :

launch_ros.actions.Node(
        package='px4',
        executable='px4',
        name='sitl',
        output='screen',
        on_exit=launch.actions.Shutdown()
    ),

But the same errors occurs :

ament_index_python.packages.PackageNotFoundError: "package 'px4' not found, searching: ['/opt/ros/foxy']"

So I thought about some ways to solve this issues :

  • locate where the package is and the path to the correct environment variable. But I didn’t understand where this package is stored and which variable should I modify ( is it $AMENT_PREFIX_PATH or $AMENT_CURRENT_PREFIX ?)
  • maybe px4 isn’t compile as a ROS2 package and it didn’t find it (yeah still the same understanding problem that I have on ROS2 package)

Ultimately what I would like to do with my ROS2 launch file is :

  1. Launch a gazebo
  2. Spawn a drone (typhoon_h480)
  3. Launch PX4 so i can communicate with it and command it with QGroundControl
  4. Launch my own ROS2 image processing node

So here it is a bit more in depth of my problem thanks you for reading me, hope it wasn’t too boring, and thanks you in advance for your answer!

That’s exactly the issue you are facing. So you rather had support to build the Firmware as an ament package, or you manually start the px4 daemon (after building with make px4_sitl gazebo) in the launch file. The daemon should be found int the build/px4_sitl/ folder.

Ok now I see more clearly, thanks you again for your answer.
I think i’ll try to build it as an ament package.

Hello @Kreace ,

Facing the same need as yours, we also wrote our own ROS 2 launchfile to launch a Gazebo simulation with an Iris running PX4, communicating over ROS 2 and controlled over QGroundControl.

We would love to contribute it to the PX4 codebase or share it with you if you still need a launchfile.

The only issue that keeps us from doing so is that since, as @TSC21 said, PX4 does not at the moment build as an Ament package, it does not install its executables and resources to a ROS 2 workspace so we had to hard-code the location of the PX4 codebase to find the executable.

We would be happy to share this launchfile with you, and if you hopefully had some time on your hands to help make PX4 build as an Ament package, it would become possible to use get_pkg_share_directory to retrieve the stuff instead of a hard-coded path and thus make this launchfile available to the larger PX4 community by contributing it.

As I already said, we would love to contribute this launchfile, the only thing is that since we got it working for ourselves we do not have enough time on our hands to modify the PX4 build system to be able to contribute it. We are a very small team.

Please let me know if you are interested.

1 Like

Hello @deb0ch,

I am looking for a solution to a problem similar to this one, as I need to control multiple vehicles running PX4 via ROS2. Could you please share the launch file you talked about? It would be really helpful both to solve my problem and to understand how to write new launch files for PX4 with ROS2.

Thank you very much!

Hello @deb0ch ,
Sorry for the late answer, I’m alone on this project and my experience with ROS2 and Ament is quite low.
I did try to build it as an ament package but nothing worked so far, and I don’t think I have the knowledge to do it.
If you have found a solution I would be happy to see it.

Hey, here is the launchfile that we are using to launch a drone running PX4 and communicating with ROS2.

You will notice that we were forced to use hard-coded paths, that you will need to update and prevent your package from being used like a regular, generic, ROS2 package. We had to do this because PX4 is not (yet) compiled as a ROS2 package and thus none of the executable, model files or Gazebo plugins are copied to the install space upon colcon build. Fixing that will allow fixing this launchfile.

Another point of improvement would be to replace the 5s sleep with a proper ROS2 Lifecycle management is ready, both in PX4 and in Gazebo-ROS.

Please credit us if you plan on integrating this launchfile (modified) into your own work or into the PX4 codebase.

"""Launch a Gazebo simulation spawning a PX4 drone communicating over ROS2."""

import os

from ament_index_python.packages import get_package_share_directory
from launch import LaunchDescription
from launch.substitutions import LaunchConfiguration
from launch.actions import IncludeLaunchDescription, DeclareLaunchArgument, ExecuteProcess, SetEnvironmentVariable
from launch.launch_description_sources import PythonLaunchDescriptionSource


def generate_launch_description():
    """Launch Gazebo with a drone running PX4 communicating over ROS 2."""
    HOME = os.environ.get('HOME')
    PX4_RUN_DIR = HOME + '/tmp/px4_run_dir'
    gazebo_launch_dir = os.path.join(get_package_share_directory('gazebo_ros'), 'launch')

    blackdrones_description_dir = get_package_share_directory('blackdrones_description')
    world = os.path.join(blackdrones_description_dir, 'worlds', 'blackdrone.world')
    model = os.path.join(blackdrones_description_dir, 'models', 'blackdrone', 'blackdrone.urdf')
    custom_gazebo_models = os.path.join(blackdrones_description_dir, 'models')
    px4_init = os.path.join(blackdrones_description_dir, 'PX4-init')

    os.makedirs(PX4_RUN_DIR, exist_ok=True)

    return LaunchDescription([
        SetEnvironmentVariable('GAZEBO_PLUGIN_PATH',
                               HOME + '/blackdrones_ws/PX4-Autopilot/build/px4_sitl_rtps/build_gazebo'),
        SetEnvironmentVariable('GAZEBO_MODEL_PATH', HOME + '/blackdrones_ws/PX4-Autopilot/Tools/sitl_gazebo/models'
                               + ':' + custom_gazebo_models),

        SetEnvironmentVariable('PX4_SIM_MODEL', 'blackdrone_gimbal'),

        DeclareLaunchArgument('world', default_value=world),
        DeclareLaunchArgument('model', default_value=model),
        DeclareLaunchArgument('x', default_value='1.01'),
        DeclareLaunchArgument('y', default_value='0.98'),
        DeclareLaunchArgument('z', default_value='0.83'),
        DeclareLaunchArgument('R', default_value='0.0'),
        DeclareLaunchArgument('P', default_value='0.0'),
        DeclareLaunchArgument('Y', default_value='0.0'),

        IncludeLaunchDescription(
            PythonLaunchDescriptionSource([gazebo_launch_dir, '/gzserver.launch.py']),
            launch_arguments={'world': LaunchConfiguration('world'),
                              'verbose': 'true'}.items(),
        ),
        IncludeLaunchDescription(
            PythonLaunchDescriptionSource([gazebo_launch_dir, '/gzclient.launch.py'])
        ),

        ExecuteProcess(
            cmd=[
                'gz', 'model',
                '--spawn-file', LaunchConfiguration('model'),
                '--model-name', 'drone',
                '-x', LaunchConfiguration('x'),
                '-y', LaunchConfiguration('y'),
                '-z', LaunchConfiguration('z'),
                '-R', LaunchConfiguration('R'),
                '-P', LaunchConfiguration('P'),
                '-Y', LaunchConfiguration('Y')
            ],
            prefix="bash -c 'sleep 5s; $0 $@'",
            output='screen'),

        ExecuteProcess(
            cmd=[
                'xterm',
                '-e',
                HOME + '/blackdrones_ws/PX4-Autopilot/build/px4_sitl_rtps/bin/px4',
                px4_init,
                '-s', px4_init + '/init.d-posix/rcS'
            ],
            cwd=PX4_RUN_DIR,
            output='screen'),
        ExecuteProcess(
            cmd=['micrortps_agent', '-t', 'UDP'],
            output='screen'),
        ExecuteProcess(
            cmd=['QGroundControl'],
            output='screen'),
    ])
1 Like

Hi, I am struggling with the same thing.
@deb0ch your launch.py file is very helpful, but still let’s some open questions.

        ExecuteProcess(
            cmd=[
                'xterm',
                '-e',
                HOME + '/blackdrones_ws/PX4-Autopilot/build/px4_sitl_rtps/bin/px4',
                px4_init,
                '-s', px4_init + '/init.d-posix/rcS'
            ],

These lines seem to be the key to success. But I am very confused by your px4_init directory. Where should this be located and what will the -s argument do?

hi, how do i do this?

Hello,

here is my launch file and how I solved it:

#!/usr/bin/env python3

import os

from ament_index_python.packages import get_package_share_directory
from launch import LaunchDescription
from launch.substitutions import LaunchConfiguration
from launch.actions import IncludeLaunchDescription, DeclareLaunchArgument, ExecuteProcess, SetEnvironmentVariable
from launch.launch_description_sources import PythonLaunchDescriptionSource
from launch_ros.actions import Node

def generate_launch_description():
    """Launch Gazebo with a drone running PX4 communicating over ROS 2."""
    HOME = os.environ.get('HOME')
    PX4_RUN_DIR = HOME + '/tmp/px4_run_dir'
    gazebo_launch_dir = os.path.join(get_package_share_directory('gazebo_ros'), 'launch')

    fpv_racing_gazebo_dir = get_package_share_directory('fpv_racing_gazebo')
    world = os.path.join(fpv_racing_gazebo_dir, 'worlds', 'labor_worlds', 'rondell.world')
    model = os.path.join(fpv_racing_gazebo_dir, 'models', 'iris_fpv_lidar', 'iris_fpv_lidar.sdf')
    #custom_gazebo_models = os.path.join(blackdrones_description_dir, 'models')
    #px4_init = os.path.join(blackdrones_description_dir, 'PX4-init')

    os.makedirs(PX4_RUN_DIR, exist_ok=True)

    return LaunchDescription([
        SetEnvironmentVariable('GAZEBO_PLUGIN_PATH',
                               HOME + '/PX4-Autopilot/build/px4_sitl_rtps/build_gazebo'),
        SetEnvironmentVariable('GAZEBO_MODEL_PATH', HOME + '/PX4-Autopilot/Tools/sitl_gazebo/models'),

        SetEnvironmentVariable('PX4_SIM_MODEL', 'iris'),

        DeclareLaunchArgument('world', default_value=world),
        DeclareLaunchArgument('model', default_value=model),
        DeclareLaunchArgument('x', default_value='0.0'),
        DeclareLaunchArgument('y', default_value='0.0'),
        DeclareLaunchArgument('z', default_value='0.0'),
        DeclareLaunchArgument('R', default_value='0.0'),
        DeclareLaunchArgument('P', default_value='0.0'),
        DeclareLaunchArgument('Y', default_value='0.0'),

        IncludeLaunchDescription(
            PythonLaunchDescriptionSource([gazebo_launch_dir, '/gzserver.launch.py']),
            launch_arguments={'world': LaunchConfiguration('world'),
                              'verbose': 'true'}.items(),
        ),
        IncludeLaunchDescription(
            PythonLaunchDescriptionSource([gazebo_launch_dir, '/gzclient.launch.py'])
        ),

        ExecuteProcess(
            cmd=[
                'gz', 'model',
                '--spawn-file', LaunchConfiguration('model'),
                '--model-name', 'drone',
                '-x', LaunchConfiguration('x'),
                '-y', LaunchConfiguration('y'),
                '-z', LaunchConfiguration('z'),
                '-R', LaunchConfiguration('R'),
                '-P', LaunchConfiguration('P'),
                '-Y', LaunchConfiguration('Y')
            ],
            prefix="bash -c 'sleep 5s; $0 $@'",
            output='screen'),
            
        ExecuteProcess(
            cmd=[
                HOME + '/PX4-Autopilot/build/px4_sitl_rtps/bin/px4',
                HOME + '/PX4-Autopilot/ROMFS/px4fmu_common/',
                '-s',
                HOME + '/PX4-Autopilot/ROMFS/px4fmu_common/init.d-posix/rcS'
            ],
            cwd=PX4_RUN_DIR,
            output='screen'),
        ExecuteProcess(
            cmd=['micrortps_agent', '-t', 'UDP'],
            output='screen'),

])

Hope this might be helpful.