Skip to the content
FixTheRobot Explore Robotics / UAV / Module 6 / M6-T8

UAV Systems Engineering · Module 6

M6-T8: ROS 2 and PX4 integration

The connection is easy to make and easy to get subtly wrong: frames, QoS, message versions and time are where integrations fail.

  • Lesson 8 of 16
  • UAV Software and Flight-Controller Integration
  • Implementation-specific
  • About 6 minutes to read

Connecting ROS 2 to PX4 is a half-hour job; making it correct is the real task. Four things decide whether the integration is trustworthy: matching message versions, compatible QoS, correct frame conversion and consistent time. This lesson is the checklist, with a conversion you can test.

Learning objectives

After this lesson, you should be able to:

  • Compare the native uXRCE-DDS path with MAVROS and choose between them.
  • Convert positions, velocities and orientations between ENU/FLU (ROS) and NED/FRD (PX4).
  • Choose QoS settings that match the PX4 DDS bridge.
  • Verify the integration with rate, frame and timestamp checks.

Conceptual explanation

Implementation-specific The details of the PX4 ROS 2 interface (message package px4_msgs, topic names with the /fmu/in and /fmu/out prefixes, QoS) follow recent PX4 documentation. The px4_msgs package must match the PX4 firmware version; mismatches cause silent or noisy failures.

Two integration paths exist:

  • Native (uXRCE-DDS): a DDS client on PX4 exchanges selected uORB topics with a micro-XRCE-DDS agent on the companion computer; ROS 2 nodes use the px4_msgs package whose message types mirror the uORB definitions. It exposes PX4’s internal topics with low overhead, and is the path PX4 documents for ROS 2.
  • MAVROS: a ROS node that translates MAVLink into ROS topics and services (and provides ENU/NED conversions). It works with PX4 and ArduPilot and gives a stable, MAVLink-level interface, at the price of the translation layer and the MAVLink message set.

Whichever path you pick, handle four issues explicitly:

  • Frames: ROS uses ENU world frames and FLU body frames (REP-103); PX4 uses NED world and FRD body. A conversion is needed on every pose, velocity, and setpoint, and the quaternion needs care.
  • QoS: PX4’s publishers use best-effort reliability with a small history (and the bridge uses transient-local durability for some topics); a subscriber with an incompatible QoS silently receives nothing.
  • Versions: px4_msgs and the firmware must come from the same release (or branch).
  • Time: PX4 timestamps are in microseconds on the vehicle’s clock; the bridge can synchronise time, but you must verify offsets before fusing anything.
Two ways to connect ROS 2 and PX4The native path uses the uXRCE-DDS agent and px4_msgs so ROS 2 sees PX4 uORB topics directly. The MAVLink path uses MAVROS to translate MAVLink into ROS 2 topics and services.ROS 2 nodesperception, plannerpx4_msgs / MAVROSmessage layerAgent / bridgeuXRCE-DDS or MAVLinkSerial / UDPlinkPX4flight controllerThe native path exposes uORB topics; the MAVROS path exposes MAVLink semantics.
The two paths. The choice affects what you can see and how.
Animation of a vehicle velocity vector turning in the horizontal plane, drawn in the east-north-up frame used by ROS and in the north-east-down frame used by PX4. The two coordinate triples change in different ways: the north and east components swap places and the vertical component flips sign. A body-frame arrow shows forward-left-up for ROS and forward-right-down for PX4, with the y and z signs flipped, and the heading in each convention is printed.
ENU ↔ NED: swap xx and yy, negate zz. FLU ↔ FRD: negate yy and zz. Yaw: ψNED=π/2−ψENU\psi _{\text{NED}}=\pi /2-\psi _{\text{ENU}}. Doing the conversion once, at the interface, in a tested function avoids mirrored motion. The numbers are computed for a vector of 3 m/s at a heading that turns through 360∘360^{\circ}; the vertical speed is 0.5 m/s up. (Looping, about 6 seconds; drawn and computed for this course with illustrative values.)

Engineering intuition

The conversion is just a fixed rotation between two right-handed conventions: ENU and NED differ by swapping xx and yy and flipping zz; FLU and FRD differ by flipping yy and zz. Everything that goes wrong is forgetting which side a number is on. Write the frame in the variable name (p_enu, v_ned) and convert at one place, in one tested function, at the boundary.

QoS problems are especially annoying because the node starts, the topic exists, and nothing arrives. Check ros2 topic info -v for the publisher’s QoS and match it, rather than relying on the default (reliable) subscription.

Mathematical formulation

Position and velocity vectors convert as (xN, yE, zD)=(yENU, xENU, −zENU)(x_{N},\,y_{E},\,z_{D})=(y_{\text{ENU}},\,x_{\text{ENU}},\,-z_{\text{ENU}}) and the inverse is the same swap. Body vectors convert FLU → FRD by (xx, −yy, −zz). For orientation, if RENU←FLU is the ROS attitude then the PX4 attitude is

Attitude conversion between conventions
RNED←FRD=Cned,enu  RENU←FLU  Cfrd,flu\mathbf R_{\mathrm{NED}\leftarrow\mathrm{FRD}} = \mathbf C_{\mathrm{ned,enu}}\;\mathbf R_{\mathrm{ENU}\leftarrow\mathrm{FLU}}\;\mathbf C_{\mathrm{frd,flu}}
Cned,enu=[01010000−1],Cfrd,flu=diag⁡(1,−1,−1)\mathbf C_{\mathrm{ned,enu}} = \begin{bmatrix}0 & 1 & 0\\ 1 & 0 & 0\\ 0 & 0 & -1\end{bmatrix}, \qquad \mathbf C_{\mathrm{frd,flu}} = \operatorname{diag}(1, -1, -1)

Both CC matrices are orthogonal with determinant +1, so each is a proper rotation (CCfrd_flu is a half-turn about the xx axis, and Cned_enu swaps xx and yy and flips zz, which is a half-turn about the axis (1, 1, 0)/2(1,\,1,\,0)/\sqrt{2}), and the product is a rotation too. Yaw converts as ψNED=π/2−ψENU\psi _{\text{NED}}=\pi /2-\psi _{\text{ENU}} (wrapped to ±π\pi ). In quaternion form, apply the same transformation through the rotation matrix or use a tested library; do not permute components by hand.

CnedC_{\text{ned}}_enu
world-frame axis permutation and sign flip.
CfrdC_{\text{frd}}_flu
body-frame sign flips.
ψ\psi
yaw angle.

Architecture and data flow

Native: PX4 uORB ↔ uXRCE-DDS client ↔ serial or UDP ↔ MicroXRCEAgent on the companion ↔ DDS ↔ ROS 2 nodes using px4_msgs (/fmu/out/* topics from PX4, /fmu/in/* topics to PX4). MAVROS: PX4 MAVLink module ↔ serial or UDP ↔ MAVROS node ↔ ROS topics (/mavros/*). A conversion node or library function sits at the boundary and the application works in ROS frames internally.

Real-world UAV applications

  • Perception and planning nodes feeding PX4 offboard setpoints.
  • Publishing VIO or lidar odometry into EKF2.
  • Recording PX4 state in ROS 2 bags alongside sensor data.
  • Hardware-in-the-loop tests with the full ROS 2 stack.

Implementation example

This runs as shown. The frame conversion functions with round-trip and handedness checks, including the quaternion conversion done through rotation matrices.

import numpy as np

C_ned_enu = np.array([[0, 1, 0], [1, 0, 0], [0, 0, -1.0]])      # world: ENU -> NED (also NED -> ENU)
C_frd_flu = np.diag([1.0, -1.0, -1.0])                           # body: FLU -> FRD (also FRD -> FLU)

def quat_to_R(q):                                                # q = (w, x, y, z)
    w, x, y, z = q
    return np.array([[1 - 2*(y*y+z*z), 2*(x*y - z*w), 2*(x*z + y*w)],
                     [2*(x*y + z*w), 1 - 2*(x*x+z*z), 2*(y*z - x*w)],
                     [2*(x*z - y*w), 2*(y*z + x*w), 1 - 2*(x*x+y*y)]])
def R_to_quat(R):
    t = np.trace(R)
    if t > 0:
        s = 2 * np.sqrt(1 + t); return np.array([s / 4, (R[2, 1] - R[1, 2]) / s, (R[0, 2] - R[2, 0]) / s, (R[1, 0] - R[0, 1]) / s])
    i = int(np.argmax(np.diag(R))); j, k = (i + 1) % 3, (i + 2) % 3
    s = 2 * np.sqrt(1 + R[i, i] - R[j, j] - R[k, k]); q = np.zeros(4); q[1 + i] = s / 4
    q[0] = (R[k, j] - R[j, k]) / s; q[1 + j] = (R[j, i] + R[i, j]) / s; q[1 + k] = (R[k, i] + R[i, k]) / s
    return q
def pose_enu_flu_to_ned_frd(p_enu, q_enu_flu):
    R = C_ned_enu @ quat_to_R(q_enu_flu) @ C_frd_flu
    q = R_to_quat(R); q = q if q[0] >= 0 else -q
    return C_ned_enu @ p_enu, q

yaw_enu = np.radians(30.0)                                       # vehicle facing 30 deg from East towards North (ENU yaw)
q_enu = np.array([np.cos(yaw_enu / 2), 0, 0, np.sin(yaw_enu / 2)])
p_ned, q_ned = pose_enu_flu_to_ned_frd(np.array([1.0, 2.0, 3.0]), q_enu)
R_ned = quat_to_R(q_ned); yaw_ned = np.arctan2(R_ned[1, 0], R_ned[0, 0])
print(p_ned, f"yaw NED {np.degrees(yaw_ned):.1f} deg (expected {np.degrees(np.pi / 2 - yaw_enu):.1f})")
assert np.allclose(p_ned, [2.0, 1.0, -3.0]) and abs(yaw_ned - (np.pi / 2 - yaw_enu)) < 1e-12
assert abs(np.linalg.det(R_ned) - 1) < 1e-12 and np.allclose(R_ned @ R_ned.T, np.eye(3), atol=1e-12)
ENU/FLU to NED/FRD conversion of a pose, checked by yaw and handedness.

Check the QoS match on the live system:

ros2 topic list | grep fmu
ros2 topic info /fmu/out/vehicle_local_position --verbose     # publisher QoS: reliability, durability, depth
ros2 topic hz /fmu/out/vehicle_local_position
Verify that topics exist, their QoS and rates. Use a subscription QoS that is compatible with the publisher.

Common engineering mistakes

Frame conversion scattered through the code

Convert in one tested function at the boundary.

Permuting quaternion components by hand

Convert through rotation matrices or a tested library.

px4_msgs from a different release than the firmware

Messages parse wrongly or are ignored; pin both.

Default (reliable) QoS subscriber

No data from best-effort publishers; set QoS explicitly.

Assuming clocks are aligned

Check the time offset between companion and vehicle before fusing.

Failure modes

Failure modes, symptoms and causes
Failure modeHow it shows upLikely cause
Topics visible but emptyros2 topic echo shows nothingQoS mismatch
Vehicle moves the wrong wayMirrored motionENU/NED sign or swap error
Message errors on the agentAgent logs deserialisation problemspx4_msgs version mismatch
Setpoints seem delayedControl lagTime-base mismatch or network buffering

Debugging approach

  1. Test the conversion with known vectors (East, North, Up) and a known yaw.
  2. Inspect QoS with ros2 topic info -v.
  3. Compare px4_msgs and firmware commit hashes.
  4. Measure topic rates and latency with timestamps.

Practical exercises

  1. Convert the East, North and Up unit vectors to NED and check against the figure in REP-103.
  2. Write a unit test for the quaternion conversion for yaws of 0∘0^{\circ}, 90∘90^{\circ}, 180∘180^{\circ}, −90∘90^{\circ}.
  3. Subscribe with matching QoS to vehicle_local_position and print xx, yy, zz.
  4. Measure the end-to-end rate and the clock offset between the companion and PX4.

Interview questions

Write an answer in your own words, then reveal the model answer. Nothing is sent anywhere.

Question 1

How do ROS and PX4 differ in frame conventions?

Model answer

ROS uses ENU world and FLU body frames; PX4 uses NED world and FRD body. Positions and velocities swap xx and yy and flip zz; body vectors flip yy and zz; yaw converts as π/2−ψ\pi /2-\psi .

Question 2

Why can a subscriber receive nothing from an existing topic?

Model answer

A QoS mismatch: for example a reliable subscriber to a best-effort publisher is not matched. Match the publisher’s reliability and durability.

Question 3

When would you choose MAVROS over the native bridge?

Model answer

When you need a MAVLink-level interface that works across autopilots or existing MAVROS-based software; the native bridge is preferred for direct access to PX4 uORB topics with lower overhead.

References and further reading

Primary papers, official documentation and standards. Each claim above that is specific to a tool or version points here.

What’s next

Mark this lesson

Saved in this browser and synced to your account when you are signed in.

Practice this lesson

M6-L4: Connect ROS 2 with PX4

Run the uXRCE-DDS agent and list topics.

Next lesson

M6-T9: uXRCE-DDS

Describe the uXRCE-DDS bridge between PX4 and ROS 2, its agent and client, topic naming, QoS and version matching.