Skip to content

px4

PX4 SITL integration for MuJoCo simulation.

This module provides a MAVLink-based interface for PX4 hardware-in-the-loop simulation with MuJoCo, following the Pegasus Simulator design pattern.

Coordinate Systems
  • MuJoCo/IsaacSim: ENU (East-North-Up) / FLU (Forward-Left-Up)
  • PX4: NED (North-East-Down) / FRD (Forward-Right-Down)

The backend automatically handles coordinate conversions internally.

Classes:

Name Description
CoordinateConverter

Coordinate system converter between ENU/FLU (MuJoCo) and NED/FRD (PX4).

SensorSource

Binary codes for MAVLink HIL_SENSOR message fields_updated field.

SensorMsg

Container for all sensor data to be sent via MAVLink.

ThrusterControl

Manages rotor control inputs received from PX4.

PX4MavlinkBackendConfig

Configuration for PX4 MAVLink backend.

PX4MavlinkBackend

MAVLink backend for PX4 SITL integration with MuJoCo.

MuJoCoPX4Config

Configuration for MuJoCo PX4 SITL simulation.

MuJoCoPX4Runner

MuJoCo runner for PX4 SITL simulation.

CoordinateConverter

Coordinate system converter between ENU/FLU (MuJoCo) and NED/FRD (PX4).

Follows Pegasus Simulator's convention. Uses [w, x, y, z] quaternion format to match MAVLink standard.

Methods:

Name Description
enu_to_ned

Convert vector from ENU to NED frame.

ned_to_enu

Convert vector from NED to ENU frame.

flu_to_frd

Convert vector from FLU to FRD body frame.

frd_to_flu

Convert vector from FRD to FLU body frame.

quat_enu_flu_to_ned_frd

Convert a [w, x, y, z] attitude from world-ENU/body-FLU to NED/FRD.

mag_body_frd

Synthesize a body-FRD magnetometer reading (gauss) from attitude.

enu_to_ned classmethod

enu_to_ned(v_enu: ndarray) -> np.ndarray

Convert vector from ENU to NED frame.

ned_to_enu classmethod

ned_to_enu(v_ned: ndarray) -> np.ndarray

Convert vector from NED to ENU frame.

flu_to_frd classmethod

flu_to_frd(v_flu: ndarray) -> np.ndarray

Convert vector from FLU to FRD body frame.

frd_to_flu classmethod

frd_to_flu(v_frd: ndarray) -> np.ndarray

Convert vector from FRD to FLU body frame.

quat_enu_flu_to_ned_frd classmethod

quat_enu_flu_to_ned_frd(q_enu_flu: ndarray) -> np.ndarray

Convert a [w, x, y, z] attitude from world-ENU/body-FLU to NED/FRD.

Uses the Pegasus convention q_ned_frd = q_enu_to_ned * q * q_flu_to_frd, with the frame swaps composed via scipy: 180 deg about [1, 1, 0]/sqrt(2) for ENU->NED and 180 deg about X for FLU->FRD. Input/output are [w, x, y, z].

mag_body_frd classmethod

mag_body_frd(quat_enu_flu: ndarray, mag_world_ned: ndarray) -> np.ndarray

Synthesize a body-FRD magnetometer reading (gauss) from attitude.

quat_enu_flu is the MuJoCo world-ENU-from-body-FLU quaternion [w, x, y, z]; mag_world_ned is the earth field in NED (gauss). The result is the field in the PX4 body-FRD frame, consistent with the true attitude so the EKF recovers the true heading.

SensorSource

Binary codes for MAVLink HIL_SENSOR message fields_updated field.

Credit: Pegasus Simulator https://github.com/PegasusSimulator/PegasusSimulator

Attributes:

Name Type Description
ACCEL int

Accelerometer update flag (0b0000000000111 = 7)

GYRO int

Gyroscope update flag (0b0000000111000 = 56)

MAG int

Magnetometer update flag (0b0000111000000 = 448)

BARO int

Barometer update flag (0b1101000000000 = 6656)

DIFF_PRESS int

Differential pressure update flag (0b0010000000000 = 1024)

SensorMsg dataclass

SensorMsg(new_imu_data: bool = False, received_first_imu: bool = False, xacc: float = 0.0, yacc: float = 0.0, zacc: float = 0.0, xgyro: float = 0.0, ygyro: float = 0.0, zgyro: float = 0.0, new_bar_data: bool = False, abs_pressure: float = 101325.0, pressure_alt: float = 0.0, temperature: float = 25.0, new_mag_data: bool = False, xmag: float = 0.0, ymag: float = 0.0, zmag: float = 0.0, new_press_data: bool = False, diff_pressure: float = 0.0, new_gps_data: bool = False, fix_type: int = 3, latitude_deg: int = 0, longitude_deg: int = 0, altitude: int = 0, eph: int = 100, epv: int = 100, velocity: int = 0, velocity_north: int = 0, velocity_east: int = 0, velocity_down: int = 0, cog: int = 0, satellites_visible: int = 10, new_vision_data: bool = False, vision_x: float = 0.0, vision_y: float = 0.0, vision_z: float = 0.0, vision_roll: float = 0.0, vision_pitch: float = 0.0, vision_yaw: float = 0.0, vision_covariance: tuple = (lambda: (0.0,) * 21)(), new_sim_state: bool = False, sim_attitude: ndarray = (lambda: np.array([1.0, 0.0, 0.0, 0.0]))(), sim_acceleration: ndarray = (lambda: np.array([0.0, 0.0, 0.0]))(), sim_angular_vel: ndarray = (lambda: np.array([0.0, 0.0, 0.0]))(), sim_lat: int = 0, sim_lon: int = 0, sim_alt: int = 0, sim_ind_airspeed: int = 0, sim_true_airspeed: int = 0, sim_velocity_inertial: ndarray = (lambda: np.array([0.0, 0.0, 0.0]))())

Container for all sensor data to be sent via MAVLink.

ThrusterControl dataclass

ThrusterControl(num_rotors: int = 4, input_offset: list = (lambda: [0.0] * 4)(), input_scaling: list = (lambda: [1000.0] * 4)(), zero_position_armed: list = (lambda: [100.0] * 4)())

Manages rotor control inputs received from PX4.

Attributes:

Name Type Description
num_rotors int

Number of rotors on the vehicle.

input_offset list

Offset values for each rotor.

input_scaling list

Scaling factors for each rotor.

zero_position_armed list

Offset when armed but zero throttle.

_input_reference ndarray

Computed angular velocities in rad/s.

Methods:

Name Description
__post_init__

Validate and initialize input reference array.

update_input_reference

Update rotor speeds from PX4 control inputs.

zero_input_reference

Set all rotor references to zero (disarmed state).

input_reference property

input_reference: ndarray

Computed angular velocities for each rotor in rad/s.

__post_init__

__post_init__()

Validate and initialize input reference array.

update_input_reference

update_input_reference(controls: Sequence[float]) -> None

Update rotor speeds from PX4 control inputs.

Parameters:

Name Type Description Default

controls

Sequence[float]

List of control values from HIL_ACTUATOR_CONTROLS.

required

zero_input_reference

zero_input_reference() -> None

Set all rotor references to zero (disarmed state).

PX4MavlinkBackendConfig dataclass

PX4MavlinkBackendConfig(vehicle_id: int = 0, connection_type: str = 'tcpin', connection_ip: str = 'localhost', connection_baseport: int = 4560, px4_autolaunch: bool = False, px4_dir: str = '', px4_vehicle_model: str = 'gazebo-classic_iris', enable_lockstep: bool = True, lockstep_timeout: float = 0.1, num_rotors: int = 4, input_offset: list = (lambda: [0.0] * 4)(), input_scaling: list = (lambda: [1000.0] * 4)(), zero_position_armed: list = (lambda: [100.0] * 4)(), reference_lat: float = 47.6061, reference_lon: float = -122.3328, reference_alt: float = 0.0, ground_left_control_index: int = 4, ground_right_control_index: int = 5, mag_field_ned: tuple = (0.25, 0.0, 0.43), baro_sea_level_hpa: float = 1013.25, baro_temperature_c: float = 25.0, imu_accel_noise_density: float = 0.004, imu_accel_random_walk: float = 0.006, imu_accel_bias_correlation_time: float = 300.0, imu_accel_turn_on_bias_sigma: float = 0.196, imu_gyro_noise_density: float = 0.0003394, imu_gyro_random_walk: float = 3.8785e-05, imu_gyro_bias_correlation_time: float = 1000.0, imu_gyro_turn_on_bias_sigma: float = 0.0087, mag_noise_density: float = 0.0004, mag_random_walk: float = 6.4e-06, mag_bias_correlation_time: float = 600.0, mag_update_rate_hz: float = 100.0, baro_pressure_noise_std: float = 0.01, baro_update_rate_hz: float = 50.0, gps_update_rate_hz: float = 5.0, gps_xy_random_walk: float = 2.0, gps_z_random_walk: float = 4.0, gps_xy_noise_density: float = 0.0002, gps_z_noise_density: float = 0.0004, gps_vxy_noise_density: float = 0.2, gps_vz_noise_density: float = 0.4, gps_bias_correlation_time: float = 60.0, noise_seed: int = 7)

Configuration for PX4 MAVLink backend.

Attributes:

Name Type Description
vehicle_id int

Vehicle identifier (affects port number).

connection_type str

MAVLink connection type (tcpin, udpin, etc).

connection_ip str

IP address for MAVLink connection.

connection_baseport int

Base port number.

px4_autolaunch bool

Whether to auto-launch PX4.

px4_dir str

Directory containing PX4-Autopilot.

px4_vehicle_model str

PX4 vehicle model name.

enable_lockstep bool

Enable lockstep simulation.

num_rotors int

Number of rotors on the vehicle.

input_offset list

Control input offsets.

input_scaling list

Control input scaling factors.

zero_position_armed list

Armed zero throttle offsets.

reference_lat float

Reference latitude for GPS simulation (degrees).

reference_lon float

Reference longitude for GPS simulation (degrees).

reference_alt float

Reference altitude for GPS simulation (meters).

ground_left_control_index int

HIL_ACTUATOR_CONTROLS index carrying the left ground-drive output-shaft speed target (rad/s). AeroCar (MAV_TYPE 30) packs it into controls[4].

ground_right_control_index int

HIL_ACTUATOR_CONTROLS index carrying the right ground-drive output-shaft speed target (rad/s). AeroCar packs it into controls[5].

mag_field_ned tuple

Synthetic earth magnetic field (gauss, NED) used to populate HIL_SENSOR mag from the ground-truth attitude when the MJCF has no magnetometer. Sent so PX4's default pre-arm heading check passes.

baro_sea_level_hpa float

Sea-level pressure (hPa) for the synthetic barometer (standard-atmosphere model from world altitude).

baro_temperature_c float

Synthetic barometer temperature (degC).

imu_accel_noise_density float

Accelerometer white-noise density (m/s^2/sqrt(Hz)).

imu_accel_random_walk float

Accelerometer bias random walk (m/s^2/s/sqrt(Hz)).

imu_accel_bias_correlation_time float

Accelerometer bias correlation time (s).

imu_accel_turn_on_bias_sigma float

Accelerometer turn-on bias sigma (m/s^2).

imu_gyro_noise_density float

Gyroscope white-noise density (rad/s/sqrt(Hz)).

imu_gyro_random_walk float

Gyroscope bias random walk (rad/s/s/sqrt(Hz)).

imu_gyro_bias_correlation_time float

Gyroscope bias correlation time (s).

imu_gyro_turn_on_bias_sigma float

Gyroscope turn-on bias sigma (rad/s).

mag_noise_density float

Magnetometer white-noise density (gauss/sqrt(Hz)).

mag_random_walk float

Magnetometer bias random walk (gauss*sqrt(Hz)).

mag_bias_correlation_time float

Magnetometer bias correlation time (s).

mag_update_rate_hz float

Magnetometer update rate (Hz).

baro_pressure_noise_std float

Per-sample pressure noise sigma (hPa).

baro_update_rate_hz float

Barometer update rate (Hz).

gps_update_rate_hz float

GPS update rate (Hz).

noise_seed int

Seed for the synthetic noise RNG (reproducible SITL). The noise prevents PX4's DataValidator from flagging STALE_DATA when the vehicle is still and synthesized mag/baro would be bit-identical.

PX4MavlinkBackend

PX4MavlinkBackend(config: PX4MavlinkBackendConfig | None = None)

MAVLink backend for PX4 SITL integration with MuJoCo.

This class handles communication between MuJoCo simulation and PX4 autopilot via MAVLink protocol. It sends simulated sensor data to PX4 and receives actuator control commands.

Usage

config = PX4MavlinkBackendConfig( vehicle_id=0, connection_type="tcpin", connection_ip="localhost", enable_lockstep=True ) backend = PX4MavlinkBackend(config) backend.start()

In simulation loop:

backend.update_imu_data(accel, gyro) backend.update_gps_data(position, velocity) backend.update(dt) controls = backend.get_actuator_controls()

Initialize the PX4 MAVLink backend.

Parameters:

Name Type Description Default

config

PX4MavlinkBackendConfig | None

Configuration object. Uses defaults if None.

None

Methods:

Name Description
start

Start the MAVLink interface and wait for PX4 connection.

stop

Stop the MAVLink interface and cleanup.

reset

Reset the backend state.

update_imu_data

Update IMU sensor data.

update_gps_data

Update GPS sensor data.

update_magnetometer_data

Update magnetometer data.

update_barometer_data

Update barometer data.

update_airspeed_data

Update airspeed sensor data.

update_ground_truth

Update ground truth state data.

update

Main update method - call this in your simulation loop.

get_actuator_controls

Get the raw actuator controls from PX4 (HIL_ACTUATOR_CONTROLS).

get_motor_commands

Get motor commands scaled to angular velocities.

get_ground_wheel_speeds

Get ground-drive wheel-speed targets received from PX4.

is_armed

Check if the vehicle is armed.

__del__

Cleanup on deletion.

start

start() -> None

Start the MAVLink interface and wait for PX4 connection.

When px4_autolaunch is set, a PX4 SITL subprocess is spawned from px4_dir using the px4_vehicle_model airframe (ported from the Pegasus PX4LaunchTool). The MAVLink listener is opened first so the spawned PX4 can connect to it.

stop

stop() -> None

Stop the MAVLink interface and cleanup.

reset

reset() -> None

Reset the backend state.

update_imu_data

update_imu_data(linear_acceleration: ndarray, angular_velocity: ndarray, frame: str = 'FLU') -> None

Update IMU sensor data.

Parameters:

Name Type Description Default

linear_acceleration

ndarray

Acceleration in m/s^2 [x, y, z] in body frame.

required

angular_velocity

ndarray

Angular velocity in rad/s [x, y, z] in body frame.

required

frame

str

Input coordinate frame, either "FLU" (MuJoCo) or "FRD" (PX4). Defaults to "FLU" for MuJoCo compatibility.

'FLU'

update_gps_data

update_gps_data(position: ndarray, velocity: ndarray, frame: str = 'ENU') -> None

Update GPS sensor data.

Parameters:

Name Type Description Default

position

ndarray

Position [x, y, z] in meters.

required

velocity

ndarray

Velocity [vx, vy, vz] in m/s.

required

frame

str

Input coordinate frame, either "ENU" (MuJoCo) or "NED" (PX4). Defaults to "ENU" for MuJoCo compatibility.

'ENU'

update_magnetometer_data

update_magnetometer_data(magnetic_field: ndarray, frame: str = 'FLU') -> None

Update magnetometer data.

Parameters:

Name Type Description Default

magnetic_field

ndarray

Magnetic field in Tesla [x, y, z] in body frame.

required

frame

str

Input coordinate frame, either "FLU" (MuJoCo) or "FRD" (PX4). Defaults to "FLU" for MuJoCo compatibility.

'FLU'

update_barometer_data

update_barometer_data(pressure: float, altitude: float, temperature: float = 25.0) -> None

Update barometer data.

Parameters:

Name Type Description Default

pressure

float

Absolute pressure in hPa (millibar), as carried by the MAVLink HIL_SENSOR abs_pressure field (PX4 multiplies by 100 to Pa).

required

altitude

float

Pressure altitude in meters.

required

temperature

float

Temperature in Celsius.

25.0

update_airspeed_data

update_airspeed_data(differential_pressure: float) -> None

Update airspeed sensor data.

Parameters:

Name Type Description Default

differential_pressure

float

Differential pressure in Pascals.

required

update_ground_truth

update_ground_truth(attitude: ndarray, angular_velocity: ndarray, linear_acceleration: ndarray, velocity: ndarray, airspeed: float = 0.0, frame: str = 'ENU_FLU') -> None

Update ground truth state data.

Parameters:

Name Type Description Default

attitude

ndarray

Quaternion [w, x, y, z].

required

angular_velocity

ndarray

Angular velocity [wx, wy, wz] in rad/s in body frame.

required

linear_acceleration

ndarray

Linear acceleration [x, y, z] in m/s^2 in body frame.

required

velocity

ndarray

Velocity [vx, vy, vz] in m/s in world frame.

required

airspeed

float

Indicated airspeed in m/s.

0.0

frame

str

Input coordinate frames, either "ENU_FLU" (MuJoCo) or "NED_FRD" (PX4). Defaults to "ENU_FLU" for MuJoCo compatibility.

'ENU_FLU'

update

update(dt: float) -> None

Main update method - call this in your simulation loop.

Sends sensor data to PX4 and polls for actuator controls.

Parameters:

Name Type Description Default

dt

float

Time step in seconds.

required

get_actuator_controls

get_actuator_controls() -> np.ndarray

Get the raw actuator controls from PX4 (HIL_ACTUATOR_CONTROLS).

Returns:

Type Description
ndarray

Array of 16 control values as received from PX4 (typically [-1, 1] range).

get_motor_commands

get_motor_commands() -> np.ndarray

Get motor commands scaled to angular velocities.

Returns:

Type Description
ndarray

Array of rotor speeds in rad/s for each motor.

get_ground_wheel_speeds

get_ground_wheel_speeds() -> np.ndarray

Get ground-drive wheel-speed targets received from PX4.

AeroCar packs left/right output-shaft angular-velocity targets (rad/s) into HIL_ACTUATOR_CONTROLS.controls alongside the rotor commands. Returns a length-2 array [left, right] in rad/s, zeroed when the vehicle is disarmed.

Returns:

Type Description
ndarray

Array [left, right] wheel-speed targets in rad/s.

is_armed

is_armed() -> bool

Check if the vehicle is armed.

__del__

__del__()

Cleanup on deletion.

MuJoCoPX4Config dataclass

MuJoCoPX4Config(mjcf_path: str, px4_config: PX4MavlinkBackendConfig = PX4MavlinkBackendConfig(), params: VehicleParams = VehicleParams(), physics_dt: float | None = None, enable_ground_drive: bool = True, ground_left_actuator: str = 'track_left_drive', ground_right_actuator: str = 'track_right_drive', ground_actuator: GroundDriveActuatorConfig = GroundDriveActuatorConfig())

Configuration for MuJoCo PX4 SITL simulation.

Attributes:

Name Type Description
mjcf_path str

Path to MuJoCo XML scene file.

px4_config PX4MavlinkBackendConfig

PX4 MAVLink configuration.

params VehicleParams

Vehicle parameters for rotor dynamics.

physics_dt float | None

Optional MuJoCo physics time step override. When unset, the time step declared by the MJCF is preserved.

enable_ground_drive bool

Apply AeroCar ground-drive wheel-speed targets from HIL_ACTUATOR_CONTROLS to the track actuators.

ground_left_actuator str

MuJoCo actuator name for the left track.

ground_right_actuator str

MuJoCo actuator name for the right track.

ground_actuator GroundDriveActuatorConfig

MuJoCo-only actuator adaptation applied to PX4's physical-side wheel-speed targets.

MuJoCoPX4Runner

MuJoCoPX4Runner(cfg: MuJoCoPX4Config)

MuJoCo runner for PX4 SITL simulation.

Integrates MuJoCo physics simulation with PX4 autopilot via MAVLink. Automatically handles coordinate conversions from MuJoCo (ENU/FLU) to PX4 (NED/FRD).

Usage

from lav2.dynamics.params import VehicleParams

config = MuJoCoPX4Config( mjcf_path="path/to/scene.xml", px4_config=PX4MavlinkBackendConfig( connection_type="tcpin", connection_ip="localhost", connection_baseport=4560, ), params=VehicleParams(), ) runner = MuJoCoPX4Runner(config) runner.run()

Initialize the PX4 SITL runner.

Parameters:

Name Type Description Default

cfg

MuJoCoPX4Config

Configuration for simulation.

required

Methods:

Name Description
apply_actions

Apply rotor dynamics and ground drive to MuJoCo actuators.

control_callback

Main control callback for MuJoCo.

load_callback

Load MuJoCo model and data, then resolve ground-drive actuators.

run

Start PX4 SITL simulation.

apply_actions

apply_actions(d: MjData)

Apply rotor dynamics and ground drive to MuJoCo actuators.

Runs every simulation step. RotorDynamics is batched (num_envs=1, num_rotors), so the RPM command is promoted to (1, N), row 0 is taken back as a 1-D array, and the 0-indexed spin/thrust/torque actuators are written. Then the AeroCar ground-drive wheel-speed targets are applied to the track actuators.

Parameters:

Name Type Description Default

d

MjData

MuJoCo data.

required

control_callback

control_callback(m: MjModel, d: MjData)

Main control callback for MuJoCo.

Sends sensor data to PX4, receives actuator commands, and applies rotor dynamics + ground drive every simulation step. PX4 control runs at the simulation frequency (no decimation) for lockstep correctness. Called automatically by MuJoCo at each step.

Parameters:

Name Type Description Default

m

MjModel

MuJoCo model (unused; kept for the mjcb_control signature).

required

d

MjData

MuJoCo data.

required

load_callback

load_callback() -> tuple[mujoco.MjModel, mujoco.MjData]

Load MuJoCo model and data, then resolve ground-drive actuators.

Returns:

Type Description
tuple[MjModel, MjData]

Tuple of (model, data).

run

run()

Start PX4 SITL simulation.