Skip to content

Fly a drone

Take off, fly a square, and land with the same code on any supported platform. You pick a backend (a firmware plus a transport), start its driver (or a simulator), set a pose source, and run a mission. Only the factory key and its config change between platforms; the flight calls stay identical.

Pick your backend in the tab below and it stays selected through install, driver, and mission code. New to the workspace? Do the Installation first.

1. Install the backend

Install control, then add the driver your vehicle uses:

make setup           # pick: control
make drone-mavros

No extra install: pymavlink ships with the core SDK. make drone-mavros adds the geoid data used for GPS altitude.

make drone-px4

No extra install: pymavlink ships with the core SDK. make drone-mavros adds the geoid data used for GPS altitude.

make drone-px4-dds
make drone-crazyflie
make drone-bebop

2. Set the pose source

For ArduPilot and PX4 the environment selects the pose source on the config:

Environment Pose source Config argument
Outdoor GPS pose_source=PoseSource.GPS
Indoor (GPS-denied) Vision (VSLAM) pose_source=PoseSource.VISION

Indoor flight

A vision-pose feed into the FCU's EKF is required indoors; see Localization. Bebop and Crazyflie fly indoors without GPS and ignore this setting.

3. Start the driver

Start the driver or bridge your mission connects to before running it (examples default to start_driver=False). Connection overrides go through env vars (FCU_URL, DEV, BAUD, IP).

make driver DRONE=mavros FCU_URL=serial:///dev/ttyUSB0:921600

Outdoor needs no bridge; the mission opens the link itself. Indoor starts the vision-pose bridge:

make driver DRONE=mavlink ENV=indoor
make driver DRONE=px4 FCU_URL=udp://:14540@127.0.0.1:14580

Outdoor needs no bridge; the mission opens the link itself. Indoor starts the vision-pose bridge:

make driver DRONE=px4_mavlink ENV=indoor
make driver-px4-dds DEV=/dev/ttyUSB0 BAUD=921600   # PORT=8888 for UDP
make driver-crazyflie
make driver-bebop IP=192.168.42.1

No hardware yet? Fly in simulation

make sim-install FIRMWARE=ardupilot   # one-time (use FIRMWARE=px4 for PX4)
make sim-start                        # terminal 1
make sim-bridge                       # terminal 2
For direct MAVLink or uXRCE-DDS add PROTOCOL=mavlink / PROTOCOL=dds on sim-bridge. Full matrix: Simulation.

4. Write the mission

Build the drone with your backend's factory key and config. The flight calls after it are the same for every backend.

import nectar
from nectar.control import DroneFactory, MavrosConfig, PoseSource

nectar.init()
drone = DroneFactory.create("mavros", MavrosConfig(pose_source=PoseSource.GPS))
import nectar
from nectar.control import DroneFactory, MavlinkConfig, PoseSource

nectar.init()
drone = DroneFactory.create("mavlink", MavlinkConfig(pose_source=PoseSource.GPS))
import nectar
from nectar.control import DroneFactory, Px4MavrosConfig, PoseSource

nectar.init()
drone = DroneFactory.create("px4", Px4MavrosConfig(pose_source=PoseSource.GPS))
import nectar
from nectar.control import DroneFactory, Px4MavlinkConfig, PoseSource

nectar.init()
drone = DroneFactory.create("px4_mavlink", Px4MavlinkConfig(pose_source=PoseSource.GPS))
import nectar
from nectar.control import DroneFactory, Px4DdsConfig, PoseSource

nectar.init()
drone = DroneFactory.create("px4_dds", Px4DdsConfig(pose_source=PoseSource.GPS))
import nectar
from nectar.control import DroneFactory, CrazyflieConfig

nectar.init()
drone = DroneFactory.create("crazyflie", CrazyflieConfig())
import nectar
from nectar.control import DroneFactory, BebopConfig

nectar.init()
drone = DroneFactory.create("bebop", BebopConfig())

Fly a 2 m square, then land:

drone.takeoff(altitude=2.0)
drone.move_to(x=2.0, y=0.0, z=0.0)
drone.move_to(x=2.0, y=2.0, z=0.0)
drone.move_to(x=0.0, y=2.0, z=0.0)
drone.move_to(x=0.0, y=0.0, z=0.0)
drone.land()
nectar.shutdown()

The bundled examples fly the same box on any backend, flown by the PID navigator where the backend supports it. Each tab is the exact command for that platform (outdoor GPS shown; for indoor flight swap --mode indoor on navigation.py or --env indoor on basic.py to the vision pose source):

nectar-activate
python3 nectar/nectar/examples/control/navigation.py \
    --drone mavros --mode outdoor --strategy pid \
    --test rectangle --altitude 2.0 --distance 2.0 --precision 0.15
nectar-activate
python3 nectar/nectar/examples/control/navigation.py \
    --drone mavlink --connection tcp:127.0.0.1:5762 \
    --mode outdoor --strategy pid \
    --test rectangle --altitude 2.0 --distance 2.0 --precision 0.15

On hardware, pass your serial link instead, e.g. --connection /dev/ttyUSB0.

nectar-activate
python3 nectar/nectar/examples/control/navigation.py \
    --drone px4 --mode outdoor --strategy pid \
    --test rectangle --altitude 2.0 --distance 2.0 --precision 0.15

navigation.py does not cover this backend; the position box in basic.py does:

nectar-activate
python3 nectar/nectar/examples/control/basic.py \
    --drone px4_mavlink --mode position \
    --height 2.0 --side 2.0 --precision 0.15 --env outdoor
nectar-activate
python3 nectar/nectar/examples/control/basic.py \
    --drone px4_dds --mode position \
    --height 2.0 --side 2.0 --precision 0.15 --env outdoor
nectar-activate
python3 nectar/nectar/examples/control/basic.py \
    --drone crazyflie --mode position \
    --height 0.5 --side 0.6 --precision 0.15

Bebop has no onboard position control, so it flies the velocity box:

nectar-activate
python3 nectar/nectar/examples/control/basic.py --drone bebop --mode velocity

Expected result

The drone arms, climbs to the target altitude, flies a 2 m box (0.6 m for Crazyflie) with 0.15 m arrival precision, then lands and disarms.

Our drone flying the square example on real hardware. Click to enlarge.

Go deeper