PX4 Firmware¶
PX4 support built on the shared vehicle core. Px4Drone implements the PX4-specific flight semantics; the transport-agnostic navigation, PID, sequencer, and movement API are inherited unchanged from VehicleDrone.
For the public
DroneAPI, navigation methods, references, altitude sources, and takeoff/land detection, see the Vehicle Core. This README covers only what is PX4-specific.
Role¶
Px4Drone (drone.py) specializes VehicleDrone for PX4:
- Offboard control: PX4 only accepts
OFFBOARDmode — and only arms in it — while setpoints stream faster than 2 Hz, and it drops out of offboard if the stream stalls for ~500 ms (PX4 offboard). A background ROS timer (the offboard pump) republishes the last commanded setpoint atoffboard_rate_hz(default 20 Hz) so the vehicle stays in offboard between explicit movement commands. - Arm sequence: seed a hold setpoint → stream it → switch to
OFFBOARD→ arm. - Takeoff: climbs to an offboard position setpoint (no mode change), reusing the velocity-based settle detection in
FlightSequencer. - Land / RTL: PX4
AUTO.LAND/AUTO.RTLmodes; RTL altitude viaRTL_RETURN_ALT, loiter-vs-land viaRTL_LAND_DELAY.
Px4Drone is transport-agnostic; it is paired with one of three backends so a mission runs unchanged on any of them:
Px4MavrosDrone(px4,mavros_drone.py) —Px4DroneonMavrosTransport, the same MAVROS plumbing the ArduPilot drones use, reached throughmavros px4.launch.Px4MavlinkDrone(px4_mavlink,mavlink_drone.py) —Px4Droneon the firmware-neutralPymavlinkTransportwith aPx4ModeCodec: a direct MAVLink link, no MAVROS. Mirrors ArduPilot'smavlinkdrone.Px4DdsDrone(px4_dds,dds_drone.py) —Px4DroneonPx4DdsTransport, native PX4 uORB over uXRCE-DDS (see below).
PX4 flight-mode encoding for the MAVLink backends lives once in modes.py (MODE_TO_PX4 + px4_mode_name), shared by Px4ModeCodec and Px4DdsTransport.
Architecture¶
classDiagram
class VehicleDrone {
<<abstract>>
+takeoff() land() move_to() move_to_gps() move_velocity() rtl()
+arm()* capabilities*
#_rtl_native()* _change_speed()*
#_command_takeoff() _command_land() _ensure_offboard_ready() _emit_velocity()
}
class Px4Drone {
-_offboard_setpoint
-_pump_timer
+arm() capabilities
+_command_takeoff() _command_land() _rtl_native()
+_ensure_offboard_ready() _emit_velocity() publish_setpoint()
+_pump_tick() _seed_hold_setpoint()
}
class Px4MavrosDrone {
+from_config(config, executor)$
}
class Px4MavlinkDrone {
+connection MavlinkConnection
+from_config(config, executor)$
}
class Px4DdsDrone {
+from_config(config, executor)$
}
class VehicleTransport {
<<abstract>>
}
class MavrosTransport
class PymavlinkTransport
class Px4DdsTransport
VehicleDrone <|-- Px4Drone
Px4Drone <|-- Px4MavrosDrone
Px4Drone <|-- Px4MavlinkDrone
Px4Drone <|-- Px4DdsDrone
VehicleDrone o-- VehicleTransport
VehicleTransport <|.. MavrosTransport
VehicleTransport <|.. PymavlinkTransport
VehicleTransport <|.. Px4DdsTransport
Px4MavrosDrone ..> MavrosTransport : builds
Px4MavlinkDrone ..> PymavlinkTransport : builds
Px4DdsDrone ..> Px4DdsTransport : builds
Flight modes¶
PX4 flight modes used by this SDK (MAVROS custom_mode strings):
| Mode | Use |
|---|---|
OFFBOARD |
Companion setpoint control (move_to / move_velocity). Required for SDK navigation. |
AUTO.LAND |
Land at the current position (land()). |
AUTO.RTL |
Return to launch (rtl(method=NATIVE)). |
Unlike ArduPilot's single GUIDED mode, PX4 separates offboard control from the autonomous takeoff/land/RTL modes, and offboard requires the continuous setpoint stream described above.
Capabilities¶
Px4Drone.capabilities is derived from the configured pose_source (see capabilities.py): PID_NAV, LOCAL_SETPOINT, VELOCITY_BODY/WORLD/TAKEOFF, ACTUATOR, GRIPPER, PARAMS, NATIVE_RTL, OBSTACLE_AVOIDANCE, RANGEFINDER, DISTANCE_SENSORS, plus GPS_NAV/GLOBAL_SETPOINT (outdoor) or VISION_POSE (indoor). PX4 declares ACTUATOR (DO_SET_ACTUATOR) and GRIPPER (DO_GRIPPER) for payloads, but not ArduPilot's per-channel PWM SERVO path (do_servo); see Payload actuators.
Indoor / external vision (EKF2)¶
For GPS-denied flight, pose_source=PoseSource.VISION makes the SDK read the FCU's fused local pose for companion-side PID navigation. The external pose itself is fed to the FCU by the separate localization bridge (vision_pose.launch.py backend:=mavros|mavlink), which sends VISION_POSITION_ESTIMATE. PX4's EKF2 fuses it once EKF2_EV_CTRL / EKF2_HGT_REF are set, GNSS is disabled (EKF2_GPS_CTRL=0), and the stream is 30–50 Hz (PX4 rejects rates that are too low — much stricter than ArduPilot's ≥4 Hz). The full parameter set, the ArduPilot equivalents, and current flight status are in Localization → FCU setup.
Configuration¶
import nectar
from nectar.control import DroneFactory, Px4MavrosConfig, PoseSource
nectar.init()
config = Px4MavrosConfig(
pose_source=PoseSource.GPS, # GPS (outdoor) or VISION (indoor)
connection_string="udp://:14540@127.0.0.1:14580", # PX4 SITL offboard API
offboard_rate_hz=20.0, # setpoint stream rate (>2 Hz)
)
drone = DroneFactory.create("px4", config)
Px4MavrosConfig carries the MAVROS topic names (identical to MavrosConfig), connection_string (a MAVROS fcu_url), offboard_rate_hz, mavros_launch (default px4.launch), and the setpoint-config fields setpoint_config_file / apply_setpoint_params (see below).
SITL presets (in config.py): PX4_SITL_CONFIG, PX4_SITL_GAZEBO_CONFIG, PX4_SITL_VISION_CONFIG.
Setpoint configuration (speed / acceleration)¶
PX4's analog of ArduPilot's WPNAV parameters is the multicopter position-controller MPC_* family. Px4SetpointConfig carries SI-unit speed/accel/jerk limits and maps them to MPC_* (see the PX4 Controller Diagrams and Parameter Reference):
| Field | PX4 param | Meaning |
|---|---|---|
speed |
MPC_XY_CRUISE |
Horizontal cruise speed |
vel_max |
MPC_XY_VEL_MAX |
Horizontal speed hard cap (raised to ≥ speed) |
speed_up / speed_down |
MPC_Z_VEL_MAX_UP / MPC_Z_VEL_MAX_DN |
Climb / descent speed |
accel |
MPC_ACC_HOR |
Horizontal acceleration |
accel_up / accel_down |
MPC_ACC_UP_MAX / MPC_ACC_DOWN_MAX |
Vertical acceleration |
jerk |
MPC_JERK_AUTO |
Trajectory jerk limit |
yaw_rate |
MPC_YAWRAUTO_MAX |
Auto yaw rate (deg/s) |
takeoff_speed |
MPC_TKO_SPEED |
Takeoff climb speed |
These govern the POSITION / POSITION_GLOBAL setpoint paths and the PX4 offboard takeoff climb. The default PID / PID_EKF path is unaffected — there the SDK computes velocity and the ceiling is the per-axis PID output clamp (see Vehicle core), identical to ArduPilot.
Set via config (apply_setpoint_params=True pushes the params to the FCU on arm()) or at runtime; set_speed changes a single axis live:
from nectar.control import DroneFactory, Px4MavrosConfig, Px4SetpointConfig
drone = DroneFactory.create("px4", Px4MavrosConfig(
setpoint_config_file="setpoint_outdoor.yaml", apply_setpoint_params=True,
))
drone.set_setpoint_config({"speed": 3.0, "accel": 2.5}) # push MPC_* to the FCU
drone.set_speed(2.0, "horizontal") # live: MPC_XY_CRUISE/VEL_MAX
Bundled presets live in the config/ directory (setpoint_outdoor.yaml, setpoint_indoor.yaml, and the setpoint_sim_* SITL variants).
Limitation: the native uXRCE-DDS backend (
px4_dds) cannot set parameters, soapply_setpoint_params/set_setpoint_config/set_speedare no-ops there — use the MAVROS or direct-MAVLink backend, or set theMPC_*params in QGC.
Payload actuators / gripper¶
For payloads (e.g. a release hook), use the firmware-neutral API on any backend:
drone.set_gripper(grab=False) # MAV_CMD_DO_GRIPPER: release (grab=True closes)
drone.set_actuator(index=1, value=1.0) # MAV_CMD_DO_SET_ACTUATOR: normalized -1..1
PX4 maps these to the Gripper and Peripheral Actuator Set output functions configured in Actuators / Servo Gripper. ArduPilot's per-channel do_servo(channel, pwm) is not available on PX4 (it declares no SERVO capability); set_actuator / set_gripper are the portable replacements.
Requirements¶
A mavros_node must be bridging the PX4 FCU. The transport launches it for you when Px4MavrosConfig.start_driver=True:
For PX4 SITL the offboard MAVLink API is on UDP 14540 (udp://:14540@127.0.0.1:14580); on hardware use the appropriate serial/UDP fcu_url.
Simulation¶
PX4 SITL with Gazebo (PX4 starts Gazebo itself), via the unified simulation CLI:
Terminal 1 — PX4 SITL + Gazebo (shared Nectar outdoor world + x500_nectar, offboard on UDP 14540; matched sensors /front_camera/*, /down_camera, and the downward lidar on /mavros/rangefinder/rangefinder):
Terminal 2 — MAVROS + camera bridges:
Run a script (MAVROS backend):
For the direct-pymavlink backend, drop MAVROS on Terminal 2 and use the
px4_mavlink drone (it connects to UDP 14540 itself; the rangefinder arrives as
MAVLink DISTANCE_SENSOR, cameras via ros_gz_bridge):
make sim-bridge FIRMWARE=px4 ENV=outdoor PROTOCOL=mavlink # cameras only, no MAVROS
python3 basic.py --drone px4_mavlink --connection udp:0.0.0.0:14540
make sim-start FIRMWARE=px4 ENV=outdoor flies PX4 in the same arena as
make sim-start FIRMWARE=ardupilot ENV=outdoor, with the same sensor topics, so
missions are firmware-agnostic. Install once with make sim-install FIRMWARE=px4.
See the
Simulation guide
for the shared-world architecture.
Usage¶
import nectar
from nectar.control import DroneFactory, PX4_SITL_GAZEBO_CONFIG
nectar.init()
drone = DroneFactory.create("px4", PX4_SITL_GAZEBO_CONFIG)
drone.takeoff(altitude=3.0)
drone.move_to(x=5.0, y=0.0, z=0.0, precision=0.3)
drone.move_to_gps(latitude=-22.413, longitude=-45.449, altitude=15.0)
drone.rtl(land=True)
nectar.shutdown()
Native uXRCE-DDS path (px4_dds)¶
A second transport reaches PX4 directly over its uXRCE-DDS bridge using px4_msgs — no MAVROS. Px4DdsDrone is Px4Drone on a Px4DdsTransport:
- Telemetry on
/fmu/out/*(VehicleLocalPosition,VehicleStatus,VehicleGlobalPosition) with thesensor_dataQoS PX4 requires. - Commands via
/fmu/in/vehicle_command(VehicleCommand); setpoints via/fmu/in/offboard_control_mode+/fmu/in/trajectory_setpoint. - PX4 NED/FRD is converted to/from the core's ENU/FLU in the transport.
Indoor external vision uses the same DDS link: the localization dds backend publishes px4_msgs/VehicleOdometry on /fmu/in/vehicle_visual_odometry for EKF2 (ros2 launch nectar vision_pose.launch.py backend:=dds). See Localization.
Requirements (one-time) — builds px4_msgs and the agent (against ROS's Fast-DDS):
Usage — 3 terminals (PX4 SITL starts the uXRCE-DDS client itself):
Terminal 1 — PX4 SITL + Gazebo:
Terminal 2 — uXRCE-DDS agent (MicroXRCEAgent udp4 -p 8888):
Run a script:
from nectar.control import DroneFactory, PX4_DDS_SITL_CONFIG
drone = DroneFactory.create("px4_dds", PX4_DDS_SITL_CONFIG)
Notes / limitations:
- Versioned topics: PX4 versions some uORB topics (e.g.
vehicle_local_position_v1,vehicle_status_v4).Px4DdsConfigexposeslocal_position_topic/status_topic/global_position_topic(defaults track current PX4main); override them to match a different firmware.px4_msgsmust be the branch matching your PX4 release. set_paramis not bridged over uXRCE-DDS by default (use QGC or the MAVROS path); nativertl(method=NATIVE)still switches toAUTO.RTLbut cannot pushRTL_RETURN_ALT.- Global (GPS) setpoints are local-frame only on the native path; use a PID method or the MAVROS path for
move_to_gpswithPOSITION_GLOBAL. - No-sudo setups: if the agent is installed to a user prefix instead of
/usr/local, add its lib dir toLD_LIBRARY_PATH(the--nativeinstall usessudo make install+ldconfig, which avoids this).
References¶
- PX4 User Guide · Basic Concepts
- Flight Modes (Multicopter) · Basic Flying (MC)
- ROS 2 User Guide · ROS 2 Offboard Control
- PX4 Simulation · Gazebo Simulation
- Shared flight logic: Vehicle core · MAVROS plumbing: MAVROS transport