Skip to main content
The external_nav mode takes a pose that another installed plugin publishes on a shared topic, such as a planar LiDAR SLAM from the World Engine extension, and sends it to the flight controller’s own estimator. The flight controller keeps doing state estimation, control and avoidance; the companion supplies one more position source and switches the EKF to it and away from it as tracking quality changes. An optional obstacle feed turns a shared planar scan into OBSTACLE_DISTANCE, so ArduPilot’s proximity avoidance sees what the LiDAR sees.
External navigation changes what the flight controller’s estimator trusts for position. Prove it on the bench and in a tethered or low hover before relying on it, keep an RC switch on the EKF source-set option, and read Safety.
External navigation and the source-set switch need ArduPilot. PX4 has no EKF source sets.

What you need

  • A plugin that publishes the pose topic (default plugin.world-engine.pose) and, for the obstacle feed, the scan topic (default plugin.world-engine.scan). The World Engine extension publishes both from a planar LiDAR when atlas.nav_pose is always.
  • A downward rangefinder or barometer on the flight controller for height. A planar pose has no height: the plugin echoes the flight controller’s own height back.
  • An EKF origin at the take-off site. Without GPS, the flight controller should set it itself at boot (see EKF origin); origin_lla in the plugin config is the last-resort copy the plugin sends when the flight controller has none.

Configure

Set the mode and the two blocks in the per-drone plugin config (see Install and configure):

Flight controller setup

With gps_primary: true, make the fallback set the GPS set (EK3_SRCn_POSXY 3, VELXY 3) and keep the ExternalNav set as extnav_set. Leave AHRS_OPTIONS bit 4 off: the GPS sets the origin.

EKF origin

ArduPilot’s EKF aligns its yaw at boot with the magnetic declination it has at that moment. An origin that arrives after the alignment makes it look up the site’s declination and step the earth field. With COMPASS_AUTODEC on (the default) that step turns into a gyro bias, and the vehicle yaws, on the ground or in flight. ArduPilot also accepts an origin while armed. Without GPS, let the flight controller set the origin itself before the alignment:
  • ArduPilot 4.7 and later: AHRS_OPTIONS bit 4 with AHRS_ORIGIN_LAT/LON/ALT at the take-off site, and EK3_MAG_EF_LIM above 0. The flight controller reports “AHRS: using recorded origin” at boot.
  • Before 4.7: COMPASS_AUTODEC 0 with COMPASS_DEC set to the site’s declination.
The plugin asks the flight controller for its origin every 5 s until it reports one. It sends SET_GPS_GLOBAL_ORIGIN (#48) from origin_lla only as a last resort, when all of these hold:
  • the flight controller has reported no origin 5 s after it booted (a heartbeat after 3 s of silence is a new boot);
  • the vehicle is disarmed;
  • gps_primary is false.
If the origin the plugin sent arrives after a compass-yaw alignment with COMPASS_AUTODEC on, the ekf_origin check blocks until the flight controller is set up as above and rebooted.

What reaches the flight controller

ODOMETRY (#331) on component 197, one message per measured pose, up to rate_hz:
  • frame_id MAV_FRAME_LOCAL_FRD, child_frame_id MAV_FRAME_BODY_FRD, the pair ArduPilot accepts.
  • x, y and yaw from the pose. A pose in a forward-left-up map frame is converted to forward-right-down.
  • z and vertical speed from the flight controller’s own LOCAL_POSITION_NED; roll and pitch from its ATTITUDE.
  • Horizontal velocity from the pose difference over the last 100 ms, rotated into the body frame.
  • Pose noise and quality by tracking health:
  • reset_counter increments when a pose lands more than 0.5 m from where the last velocity predicts it (from the last pose after a gap over 0.5 s or a loss): a relocalisation, not motion, so steady flight at any speed never counts.
EKF origin. See EKF origin: SET_GPS_GLOBAL_ORIGIN (#48) from origin_lla, only as a last resort. OBSTACLE_DISTANCE (#330), from the latest scan:
  • frame MAV_FRAME_BODY_FRD, increment_f = 360 / sectors degrees, sector 0 straight ahead, clockwise.
  • sensor_type MAV_DISTANCE_SENSOR_LASER, min_distance 5 cm, max_distance 1200 cm.
  • Each sector carries the nearest return in centimetres; a sector with no return carries max_distance + 1.

Source-set switching

The plugin sends MAV_CMD_SET_EKF_SOURCE_SET (42007) when the inputs call for a change. Nothing is commanded before its first decision, so the flight controller keeps its boot set until then. degraded holds whichever set is active. You can also switch by hand from the cockpit’s navigation-source chip, which sends the agent’s ekfSourceSet command.

Pre-arm checks

The plugin reads the parameters fc_params and ekf_origin need (VISO_TYPE, VISO_DELAY_MS, COMPASS_AUTODEC, AHRS_OPTIONS, AHRS_ORIGIN_LAT/LON, EK3_MAG_EF_LIM and the two source sets’ POSXY, YAW and VELXY) with PARAM_REQUEST_READ, every 10 s while any is missing and again after the flight controller reboots. The median age of a pose when its ODOMETRY is sent (over the last 50 poses) is published as navigation.poseAgeMs; set VISO_DELAY_MS to it. As in the other modes, the pre-arm card is advisory and does not stop the flight controller from arming. The plugin publishes nav.state on the event bus. The agent re-serves it as the nav kind of its navigation routes, and every operator surface reads it for the navigation-source chip.
Fields a mode does not produce are null.

Permissions

Next steps

Modes and fallback

All four modes and their checks.

World Engine

The planar LiDAR pose and scan source.
Last modified on November 5, 2026