Skip to content

Writing Missions

This guide is for operators and technicians who need to create or edit a vehicle mission. You do not need to be a software engineer, but you do need to be comfortable editing a text file carefully.

What a mission is

A mission is a Python file that tells the vehicle what to do, step by step.

Typical tasks in a mission are:

  • wait
  • dive
  • hold a heading
  • follow waypoints
  • land on the seabed
  • take off again
  • surface

The mission manager runs each task in order. When one task completes, it moves to the next.

One mission, many tasks

You do not combine whole Mission objects together.

The normal pattern is:

  • create one Mission()
  • add several tasks to it
  • let those tasks run in sequence

So when people talk about "combining missions", what they usually mean in practice is combining several behaviours or task steps inside one mission file.

Some higher-level behaviours are also built internally from smaller behaviours, but that still happens inside a single mission.

The four rules every mission file must follow

  1. The file must be a Python file.
  2. The filename must end with mission.py.
  3. The file must create a variable called mission.
  4. That mission variable must be built from Mission().

This is the minimum valid structure:

from arl_mission import Mission

mission = Mission()

A simple mission you can copy and edit

This example:

  • waits 10 seconds
  • turns to heading 90 degrees
  • follows a short waypoint track
  • idles at the end
from arl_mission import Mission
from arl_mission.conditions import Timeout
from arl_mission.interfaces import ControlDemand

mission = Mission()

mission.idle(
    until=Timeout(10),
    name="Wait before starting",
)

mission.manual_control(
    ControlDemand(
        orientation=[None, None, 90],
    ),
    until=Timeout(8),
    name="Set heading east",
)

mission.follow_waypoints(
    waypoints=[
        (50.89133, -1.39500, 2.0),
        (50.89123, -1.39470, 2.0),
        (50.89157, -1.39472, 2.0),
    ],
    heading_start=90,
    surge=40,
    turning_radius=10,
    until=Timeout(300),
    name="Transit route",
)

mission.idle(
    until=Timeout(10),
    name="Mission complete",
)

How to read that example

  • mission = Mission() creates an empty mission.
  • Each mission.something(...) line adds one task to the queue.
  • Tasks run from top to bottom.
  • name="..." is the label that operators will see.
  • until=... tells the task when it has finished successfully.
  • unless=... tells the task when it should abort instead.

The most useful behaviours

These are the behaviours most operators are likely to use.

mission.idle(...)

Use this to wait without asking the vehicle to move.

Typical use:

  • give recording time to start
  • create a pause between two actions
  • hold the vehicle still at the end of a mission

Example:

mission.idle(until=Timeout(15), name="Wait for recording")

mission.manual_control(...)

Use this when you want to send a direct control demand such as heading, depth, thrust, or actuator settings.

The most important fields inside ControlDemand(...) are:

  • forces=[surge, sway, heave]
  • position=[x, y, depth]
  • orientation=[roll, pitch, heading]
  • actuators=[port_thruster, stbd_thruster, vert_thruster, moving_mass]

Use None for anything you do not want to command.

Example: hold heading 225 degrees for 10 seconds:

mission.manual_control(
    ControlDemand(orientation=[None, None, 225]),
    until=Timeout(10),
    name="Set initial heading",
)

mission.follow_waypoints(...)

Use this for most survey-style missions.

The waypoint list format used in this codebase is:

(latitude, longitude, depth)

Example:

mission.follow_waypoints(
    waypoints=[
        (50.89133, -1.39500, 2.0),
        (50.89123, -1.39470, 2.0),
        (50.89157, -1.39472, 2.0),
    ],
    surge=40,
    heading_start=225,
    turning_radius=10,
    until=Timeout(300),
    name="Waypoint leg",
)

Useful settings are:

  • surge: forward thrust demand
  • heading_start: heading to assume at the start, in degrees
  • turning_radius: minimum turn radius in metres
  • until=Timeout(...): maximum time allowed for the whole leg

mission.dive(...), mission.surface(...), mission.land(...), mission.take_off(...), mission.seabed(...)

Use these higher-level behaviours when they match the operation you want. They are usually easier to read than building the same action from low-level manual control.

Examples:

from arl_mission.conditions import AtDepth, Landed

mission.dive(until=AtDepth(5), name="Dive to 5 m")
mission.land(until=Landed(), name="Land on seabed")
mission.seabed(until=Timeout(30), name="Record on seabed")
mission.take_off(name="Leave seabed")
mission.surface(until=Timeout(20), name="Surface")

Conditions: how tasks know when to stop

Conditions are the normal way to finish or abort a task.

There are two main ways they are used on a task:

  • until=...: if this condition is met, the task finishes successfully
  • unless=...: if this condition is met, the task aborts

Both until and unless can take:

  • a single condition
  • a list of conditions

If you pass a list, the conditions are treated as "any of these". In other words, the task will finish or abort as soon as the first matching condition becomes true.

The most common ones are:

  • Timeout(seconds): stop after a fixed time
  • AtDepth(depth_metres): stop when the vehicle reaches a depth
  • Landed(): stop when landing is detected
  • DepthRate(...): can be used to detect very low change in depth

Most missions should include a Timeout(...) somewhere, especially for long-running tasks, so the vehicle is not left trying to do one thing forever.

Examples:

mission.idle(until=Timeout(20))
mission.dive(until=AtDepth(3))
mission.land(until=Landed())

You can also abort a task if something goes wrong by using unless=....

Example:

mission.dive(
    until=AtDepth(5),
    unless=Timeout(60),
    name="Dive to 5 m",
)

This is useful when there is a normal success condition, but you also want a safety stop if the task takes too long or the situation is no longer acceptable.

Using multiple conditions together

You can give more than one condition to until or unless by passing a list.

Examples:

from arl_mission.conditions import AtDepth, Landed, Timeout

mission.land(
    until=[Landed(), Timeout(120)],
    unless=[AtDepth(50)],
    name="Land with timeout and depth safety abort",
)

In that example:

  • the task succeeds if landing is detected
  • the task also succeeds if it reaches the 120 second timeout first
  • the task aborts if the abort condition becomes true

This is useful when you want several acceptable ways for a task to end, or several safety limits that should stop it.

Follow-on actions after an abort

You can attach follow-on behaviours to a condition using on_abort=....

If that condition causes the task to abort, the follow-on behaviours are inserted at the front of the queue before the rest of the mission continues. This is useful for recovery actions such as stopping, idling, or surfacing.

Important:

  • on_abort is attached to the condition
  • the follow-on items are behaviour objects
  • this is different from calling mission.surface(...), which adds a normal task to the main mission queue

Example:

from arl_mission.behaviours import Surface
from arl_mission.conditions import AtDepth, Timeout

mission.dive(
    until=AtDepth(5),
    unless=Timeout(
        60,
        on_abort=[
            Surface(
                until=Timeout(20),
                name="Abort recovery: surface",
            ),
        ],
    ),
    name="Dive to 5 m",
)

In that example:

  • if the vehicle reaches 5 m depth, the task succeeds
  • if 60 seconds pass first, the task aborts
  • the recovery Surface(...) behaviour is run
  • after that, the mission manager can continue with the remaining tasks in the mission

Using a CSV file for waypoints

follow_waypoints can also load a CSV file instead of a hard-coded waypoint list.

Common column names that are recognised include:

  • longitude, latitude, depth
  • lon, lat, depth
  • easting, northing, altitude

Example:

from pathlib import Path

waypoint_file = Path(__file__).with_name("harbour_track.csv")

mission.follow_waypoints(
    waypoints=str(waypoint_file),
    until=Timeout(900),
    name="Harbour survey",
)

Using Path(__file__) is a good habit because it keeps the mission and its CSV tied together in the same folder.

Where missions must be placed for flight

For the standard flight setup, the mission manager looks at the following location on the vehicle Pi:

~/missions

Inside that folder, it only checks mission slots:

  • ~/missions/0
  • ~/missions/1
  • ~/missions/2
  • ~/missions/3
  • ~/missions/4
  • ~/missions/5
  • ~/missions/6
  • ~/missions/7

Each folder number is the mission ID used when starting the mission.

Example:

  • file path: ~/missions/1/harbour_survey_mission.py
  • mission ID to start: 1

Important details:

  • Only folders 0 to 7 are scanned.
  • The mission filename must end with mission.py.
  • If more than one *mission.py file is in the same slot, the alphabetically first one is chosen.
  • In practice, keep only one mission file in each slot folder.
  1. Copy an existing mission that is close to what you want.
  2. Edit names, waypoints, depths, headings, and timeouts.
  3. Save the file with a name ending in mission.py.
  4. Put it in the correct slot under ~/missions/<id>/.
  5. Keep any CSV waypoint file in the same folder as the mission if possible.
  6. Check the mission appears in /flying_node/list_missions.
  7. Run it in simulation, a tank, or another low-risk environment before open-water use.

Good habits

  • Give every task a clear name.
  • Add comments explaining why a step exists.
  • Use Timeout(...) on long actions.
  • Start simple and build up.
  • Prefer follow_waypoints(...) over complex custom waypoint code unless you need something unusual.
  • Keep one mission per slot to avoid starting the wrong file.

Common mistakes

  • Forgetting to create mission = Mission()
  • Saving the file with a name that does not end in mission.py
  • Putting the mission in the wrong folder
  • Using the wrong mission ID when starting it
  • Putting waypoint tuples in the wrong order
  • Forgetting that heading values are written in degrees
  • Forgetting to include a stopping condition such as Timeout(...)

Useful examples in the Flight Software Repository

If you want working examples to copy from, start with:

  • missions/1/example_waypoint_navigation_mission.py
  • missions/0/e2e_mission.py
  • missions/waypoint_constant_depth.py