[PX4 ROS 2 Programming] Part 2: Essential Precautions for ROS 2 Programming (QoS, Coordinate Frames, and Time Sync)

Hello and welcome back, robotics and autonomous flight enthusiasts!

In Part 1, we explored the concepts of the newly introduced uXRCE-DDS middleware and successfully built the ROS 2 Humble and PX4 workspaces in an Ubuntu 22.04 environment. If you have successfully set up your environment, it is time to write some C++ code and start controlling the drone.

However, many university students and beginner researchers encounter a “wall of despair” at this exact stage. Even though the ROS 2 node code is syntactically perfect and compiles without any error logs, you might experience baffling situations such as “no sensor data coming in at all” or “the drone crashing into the ground the moment a takeoff command is sent.”

Nine times out of ten, these issues arise from a lack of understanding regarding the differences in configurations and conventions between the ROS 2 and PX4 systems. Therefore, before we dive into writing control nodes, this Part 2 will thoroughly and kindly break down three core concepts that will drastically reduce your debugging time: QoS (Quality of Service), Coordinate Frames, and Time Synchronization.


1. The First Precaution: QoS (Quality of Service) Profile Settings

One of the most frequently asked questions on forums is this: “I can see the data perfectly fine using the ros2 topic echo command in the terminal, but the callback function in my custom C++ subscriber node is never triggered!”

The root cause of this phenomenon is a mismatch in QoS (Quality of Service) settings.

What is QoS (Quality of Service)? In ROS 2, when nodes communicate, you can determine the quality of the communication based on the nature of the data. For instance, a “takeoff command,” where even a single packet loss can be critical, requires a different communication strategy (e.g., guaranteed reliability) compared to “IMU sensor data,” where getting the most recent data with minimal latency is more important than guaranteeing the delivery of every single packet.

The Cause of the Problem: Most topics published by PX4 to ROS 2 via uXRCE-DDS use a QoS profile suited for sensor data (Best Effort). However, if you create a ROS 2 subscriber without specifying any settings, it adopts the ROS 2 default QoS setting (Reliable). Unfortunately, the ROS 2 default QoS setting is incompatible with PX4’s publisher QoS settings. Because the communication protocols do not match, the ROS 2 subscriber completely ignores the data sent by the publisher, resulting in the callback function never being executed.

The Solution: Use rmw_qos_profile_sensor_data You must explicitly apply a sensor data QoS profile to your ROS 2 subscriber code. Below is an example of a C++ subscriber that correctly applies the QoS settings to receive PX4’s SensorCombined (accelerometer and gyroscope) data.

C++
#include <rclcpp/rclcpp.hpp>
#include <px4_msgs/msg/sensor_combined.hpp> // Include PX4 message header

class SensorCombinedListener : public rclcpp::Node
{
public:
    explicit SensorCombinedListener() : Node("sensor_combined_listener")
    {
        // 1. Fetch the QoS profile struct for sensor data (This is the key part!)
        rmw_qos_profile_t qos_profile = rmw_qos_profile_sensor_data;
        
        // 2. Initialize the rclcpp::QoS object (Set history depth to 5 to keep the latest 5 messages)
        auto qos = rclcpp::QoS(rclcpp::QoSInitialization(qos_profile.history, 5), qos_profile);

        // 3. Pass the qos setting as an argument when creating the subscriber
        subscription_ = this->create_subscription<px4_msgs::msg::SensorCombined>(
            "/fmu/out/sensor_combined", 
            qos,  // <--- Applying the compatible QoS profile
            [this](const px4_msgs::msg::SensorCombined::UniquePtr msg) {
                // Callback function executed upon data reception
                RCLCPP_INFO(this->get_logger(), "Acceleration X: %f", msg->accelerometer_m_s2);
            });
    }

private:
    rclcpp::Subscription<px4_msgs::msg::SensorCombined>::SharedPtr subscription_;
};

As shown in the code above, by passing the qos object based on rmw_qos_profile_sensor_data as the second argument to create_subscription, you will see the previously blocked data flowing in seamlessly. (Note: When sending control commands from ROS 2 to PX4, the default ROS 2 settings are compatible with PX4’s settings, so you generally don’t need to alter the QoS for publishers).


2. The Second Precaution: The Trap of Coordinate Frames

If you successfully establish communication and send a position control command like “go up 5 meters” (z = 5.0), but the drone tries to dig into the ground or flies off in a completely wrong direction, you must check for Coordinate Frame mismatches.

ROS 2 and PX4 use completely different default coordinate frame conventions. If you transmit data without converting between the two, the system will interpret your commands in the exact opposite directions.

(1) World Frame (Global Position)

  • PX4 (NED): Uses the North, East, Down system. Moving forward relative to the vehicle is North (X), moving right is East (Y), and pointing downwards is the positive (+) Z-axis. As altitude increases, the Z-value becomes negative (-).
  • ROS 2 (ENU): Uses the East, North, Up standard system. Moving right is East (X), moving forward is North (Y), and pointing upwards is the positive (+) Z-axis.

(2) Body Frame (Vehicle Attitude and Thrust)

  • PX4 (FRD): Uses the Forward, Right, Down system. The front of the drone is X, the right side is Y, and the bottom is Z.
  • ROS 2 (FLU): Uses the Forward, Left, Up system. The front of the drone is X, the left side is Y, and the top is Z.

How do we convert them?

Unless explicitly specified otherwise in the message definition, all PX4 topics inherently follow the NED and FRD conventions. Therefore, you must perform explicit mathematical rotations to align the coordinate frames when exchanging data.

  • ENU (ROS) NED (PX4) Conversion (World Conversion): Used when sending TrajectorySetpoint messages to dictate target positions. First, you must rotate π/2 (90 degrees) around the Z-axis (up), and then rotate π (180 degrees) around the new X-axis (the old East/new North).
  • FLU (ROS) FRD (PX4) Conversion (Body Conversion): Used when sending VehicleThrustSetpoint messages to control thrust. A single π (180 degrees) rotation around the X-axis (front) is sufficient.

💡 Development Tip: Calculating these rotations manually using trigonometric functions and quaternions every time is highly tedious and error-prone. To solve this, the px4_ros_com package includes a very useful shared library called frame_transforms. By including this library at the top of your C++ code, you can use its helper functions to safely and easily convert vectors and quaternions (attitude data).


3. The Third Precaution: Time Synchronization

In robotic systems, especially for drones that fly at high speeds and need to fuse various sensor data, Time is literally of the essence. You must know exactly ‘when’ a piece of data was generated (Timestamp) to run proper control algorithms. Whether you are in a simulation environment or controlling an actual vehicle, understanding this time synchronization principle is crucial.

Scenario A: Using the OS Clock as the Main Source (Standard Situation)

Most standard ROS 2 nodes use the computer’s OS system time. The uXRCE-DDS middleware is designed to be incredibly smart; it automatically calculates time offsets, time drift, and communication latency between the flight controller (PX4 client) and the companion computer (ROS 2 agent), and synchronizes the time on its own. You can monitor this synchronization status via the /fmu/out/timesync_status bridged topic. In typical situations where the simulation’s Real Time Factor (RTF) is close to 1, developers can comfortably use the system time without any extra coding.

Scenario B: Using the Gazebo Simulator Clock (Special Situations)

However, if for research purposes you need to run the simulation faster or slower than real-time (i.e., RTF is not 1), or in complex environments where ROS 2 needs to interact directly with the Gazebo simulator, the OS time synchronization method will not work properly.

In this case, both the ROS 2 nodes and PX4 must use Gazebo simulator’s internal time (the /clock topic) as their reference, rather than the OS time. Here is the 3-step procedure to achieve this:

  1. Establish a ROS 2 Gazebo Bridge: Use the parameter_bridge node from the ros_gz_bridge package to bring Gazebo’s clock data into the ROS 2 /clock topic:
  2. Modify ROS 2 Parameters: You must instruct all of your custom ROS 2 nodes to use the simulation time instead of the OS time by setting the use_sim_time parameter to true.
  3. Modify PX4 Parameters: On the PX4 side, you need to stop uXRCE-DDS from attempting its own OS time synchronization. You can do this by setting the PX4 system parameter UXRCE_DDS_SYNCT to false.

By configuring these settings, Gazebo acts as the perfect ‘master clock’ for the entire system. If you pause the simulation, the control logic time of your ROS 2 nodes will stop precisely in sync.


Wrapping Up

In this Part 2, we took a deep dive into the three core pillars you must embed in your mind before seriously writing code with ROS 2:

  • QoS (rmw_qos_profile_sensor_data) settings to prevent missing data due to compatibility issues.
  • Coordinate Frame transformations (ENU NED, FLU FRD) for accurate drone orientation and thrust control.
  • Accurate Time Synchronization strategies tailored to your simulation environment and goals.

If you fully grasp just these three concepts, I guarantee you will save 90% of the debugging time you would otherwise spend wrestling with mysterious errors! While the theory might seem a bit overwhelming right now, it will become second nature once we implement it in code.

In the next Part 3: Writing Basic ROS 2 Nodes (Listener & Advertiser), we will utilize the knowledge we learned today to receive the SensorCombined topic and print it to the terminal. Going a step further, we will go line-by-line through a real C++ example to shoot debugging data from ROS 2 into the PX4 network via the DebugVect topic. See you in the next part!


YouTube Class

재생


Author: maponarooo, CEO of QUAD Drone Lab

Date: February 27, 2026

Similar Posts

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다