The Dashboard lets an operator draw a MAVLink-format waypoint mission directly on the Mapbox map, tune each waypoint’s command/altitude/parameters, reorder the sequence by drag-and-drop, upload it to a vehicle, and start it. Every waypoint is a POI that maps 1:1 onto an ArduPilot/Mission Planner mission item, so the Dashboard, Gateway, and flight controller all speak the same frame/command/param contract. This page covers the build → upload → execute loop entirely from the frontend’s perspective. For the on-map rendering of waypoint spheres and flight-path lines see Map & Live Vehicle Tracking; for the Gateway’s mission REST endpoints and MAVLink storage format see the backend pages linked below.

Missions & Geofences API

Gateway REST surface for mission/point CRUD and push_mission / start_mission actions.

Mission & Geofence MAVLink Format

How the Gateway stores waypoints and converts them for upload (to_mavlink_waypoint).

DroneControlService & Rosbridge

Where push_mission/start_mission dispatch to the vehicle over rosbridge.

Map & Live Vehicle Tracking

Waypoint sphere rendering, flight-path lines, and AUTO-mode line advancement.
Map view used for mission planning

The POI (waypoint) data model

A mission is a Mission { id, name, mission_points: POI[] }. Each POI (src/app/models/poi.model.ts) carries both a human-friendly identity and the MAVLink waypoint fields that Mission Planner uses:
FieldTypeNotes
idstringEmpty string for a not-yet-persisted point
lat / lngnumberWGS84 degrees; become x_lat / y_long on upload
altitudenumberMeters; becomes z_alt on upload
typebase | fly | safepointUI marker color / semantics (safepoint is excluded from the flight-path line)
labelstring?Display label (defaults to Point N)
sequencenumber1-based order; drives OrderList and map line ordering
mission_idnumber?Owning mission
framenumber?0 = GLOBAL, 3 = GLOBAL_RELATIVE_ALT (default), 2 = MISSION
commandnumber?MAVLink command (default 16) — see table below
is_currentboolean?Whether this is the active waypoint (default false)
autocontinueboolean?Always sent as true from the editor — ArduPilot ignores it for waypoints
param1param4number?Command-specific MAVLink parameters (default 0.0)
The frontend POI interface mirrors the Gateway MissionPoint ORM model exactly (skyhub_gateway_service/src/models/mission_point.py). If you add a waypoint field you must add it in both places plus to_dict / to_mavlink_waypoint on the backend, or it will be silently dropped on upload.
The editor exposes five commands. Their labels, icons, and per-command parameter meanings are hard-coded in three places that must stay in sync: edit-marker-dialog.component.ts (form options + paramConfigs), edit-mission.component.ts (commandTypes / commandIcons sidebar labels), and home.component.ts (map rendering skip-list).
CommandMAVLink nameSidebar label / iconNeeds lat/lng?Notable params
16MAV_CMD_NAV_WAYPOINTWaypoint · pi-map-markerYesparam1 = hold time (s)
22MAV_CMD_NAV_TAKEOFFTakeoff · pi-arrow-upYesparam1 = min pitch (plane), param4 = yaw
21MAV_CMD_NAV_LANDLand · pi-arrow-downYesparam4 = yaw angle
20MAV_CMD_NAV_RETURN_TO_LAUNCHRTL · pi-homeNotakes no parameters
178MAV_CMD_DO_CHANGE_SPEEDSet Speed · pi-gaugeNoparam1 = speed type, param2 = target m/s
RTL (20) and Set Speed (178) carry no position. The editor disables the lat/lng/altitude form controls for these commands (updateCoordinateFieldsState in edit-marker-dialog.component.ts:172), and home.component.ts skips them when drawing waypoint spheres and the flight-path line (renderMission / extractMapLinesPoints, skip-list [20, 178]). A command added without wiring these two skip-lists will render a bogus marker at (0,0).

Building a mission on the map

1

Create the mission

The create-mission dialog calls MissionService.creatMission(name)POST {environment.url}/mission. On success it emits the new Mission; HomeComponent.closeEditMissionModal sets it as the current mission (AppStateService.setCurrentMission) and flips on edit mode (setEditMissionMode(true)). Note the method name is intentionally creatMission (missing “e”) in mission.service.ts:14.
2

Enter edit mode

editMissionMode$ opens the EditMissionComponent sidebar and sets the map cursor to a crosshair. Mission edit and geofence edit modes are mutually exclusive by construction in AppStateService.
3

Click the map to add a waypoint

Map clicks are handled by HomeComponent.onMapClickAddMarker (home.component.ts:1871). When a mission is loaded and edit mode is on (the mission && editMode branch, ~line 2062), it drops a Three.js sphere via addMissionPoint, builds a draft POI (default type: 'fly', altitude: 50, next sequence), and opens the edit-marker dialog. Ruler mode, geofence-point mode, pick-location mode, and go-to mode are checked earlier in the same handler and take precedence.
4

Tune the waypoint in the edit-marker dialog

EditMarkerDialogComponent (edit-marker-dialog.component.ts) builds a reactive form with label, type, lat, lng, sequence, altitude, frame (default 3), command (default 16), autocontinue (forced true), and param1param4. The visible parameter labels change per selected command via currentParamConfigs. “Pick from map” (startPickingLocation) lets you re-click the map to set coordinates.
5

Save (create or update)

onSave branches on whether currentMarkerData.id exists. New points call MissionService.createMissionPoint([{ mission_id, ...formValue }])POST /mission/point; existing points merge into the ordered array and call updateMissionPointsPATCH /mission/{id}/points. Both return the updated Mission, which flows back through onMissionPointSavesetCurrentMission, and the currentMission$ subscription re-runs renderMission.

Adding a point at the vehicle’s current position

The sidebar’s Add button calls EditMissionComponent.onPointAddCurrentPos (edit-mission.component.ts:102). It reads the selected drone’s live position from AppStateService.getCurrentDronePosition(droneId) (a [lng, lat, alt] tuple from telemetry), assigns the next sequence, and defaults altitude from the telemetry Z value — except rovers, which are pinned to altitude: 0. It then emits pointEdit so the operator can review the point in the dialog before saving.

Reordering waypoints (OrderList)

The EditMissionComponent sidebar renders waypoints in a PrimeNG p-orderList with [dragdrop]="true", sorted by sequence (edit-mission.component.html:33). Dragging fires onReorder, which:
  1. Rewrites sequence = index + 1 across the whole list based on the new visual order.
  2. Persists via MissionService.updateMissionPoints(reorderedPoints, missionId)PATCH /mission/{missionId}/points.
  3. On success calls setCurrentMission(updatedMission) to re-render the map; on error it reverts the local array to the last-known sorted order.
sequence is the single source of truth for order everywhere — the sidebar sort, the map flight-path line, and the backend upload order. Always renumber contiguously from 1 after any structural edit; gaps or duplicates will desync the sidebar from the map.

Component & service reference

FileRole in mission planning
src/app/services/mission.service.tsREST client: creatMission, createMissionPoint, updateMissionPoint, updateMissionPoints (bulk reorder), getMissions, deleteMissionPoint, plus updateCurrentMissionPointIndex (advances the active waypoint when within 3 m, used for AUTO-mode line drawing).
src/app/home/edit-mission/edit-mission.component.tsWaypoint sidebar: OrderList reorder, command name/icon maps, add-at-current-position, emits uploadMission.
src/app/home/dialogs/edit-marker-dialog/edit-marker-dialog.component.tsPer-waypoint form: command/frame options, per-command paramConfigs, coordinate gating, create vs update save.
src/app/home/home.component.tsMap-click add (onMapClickAddMarker), renderMission, onUploadMission, mission edit-mode wiring.
src/app/services/drone-service/drone.service.tspushMission(droneId, missionId) and startMission(droneId, takeoffAltitude?) action clients.
src/app/home/dialogs/takeoff-altitude-dialog/takeoff-altitude-dialog.component.tsPrompts for takeoff altitude, calls startMission, then monitors altitude.
src/app/home/dialogs/mission-confirmation-dialog/mission-confirmation-dialog.component.ts15 s countdown → switch to AUTO (or RTL on cancel).

Uploading a mission to the vehicle

The sidebar Upload button → EditMissionComponent emits uploadMissionHomeComponent.onUploadMission (home.component.ts:3115). It validates that a mission, a selected drone, and at least one waypoint exist, shows the progress bar, then calls:
src/app/services/drone-service/drone.service.ts
pushMission(droneId: string, missionId: string): Observable<any> {
  return this.http.post<any>(`${environment.url}/drone/action/push_mission`, {
    drone_id: droneId,
    mission_id: missionId,
  });
}
On success the toast reports response.result.waypoints_transferred and the selected drone’s mission_id is updated locally (setSelectedDrone + droneSubscriptionService.updateDroneInList).

What the Gateway does on upload

DroneControlService.push_mission (skyhub_gateway_service/src/service/drone_control_service.py:784) loads the stored MissionPoints, converts each with to_mavlink_waypoint (lat → x_lat, lng → y_long, altitude → z_alt), and prepends a synthetic TAKEOFF (command 22, frame 3, lat/lng 0,0) only if the first stored command isn’t already 22. It then calls the rosbridge /mavros/mission/push service and asserts wp_transfered == len(waypoints) before recording drone.mission_id.
The backend uploads your stored waypoints as-is plus that one TAKEOFF prepend. The older CLAUDE.md description of an auto-generated reversed return path / RTL / speed item is stale — no such synthesis happens. Don’t design the editor around waypoints the backend will “add for you”; if you want an RTL or Set Speed item, add it explicitly as a 20 / 178 waypoint.

Starting (executing) a mission

Starting is a separate action from uploading and is driven from the flight-control bar, not the mission sidebar. MissionControlComponent.onStartMission validates the mission has points, then opens the takeoff-altitude dialog in start_mission mode. Step by step:
  1. TakeoffAltitudeDialogComponent.confirm (takeoff-altitude-dialog.component.ts:76) calls DroneService.startMission(droneId, altitude)POST /drone/action/start_mission with { drone_id, takeoff_altitude }.
  2. Gateway start_mission (drone_control_service.py:615) re-uploads the mission, sets GUIDED, arms, and issues a GUIDED takeoff to the requested altitude. It deliberately does not switch to AUTO itself.
  3. The dialog monitors altitude by subscribing to websocketService.relAltSubject; when relative altitude reaches target - 0.5 m it opens the mission-confirmation dialog.
  4. MissionConfirmationDialogComponent runs a 15 s countdown, then calls DroneService.setMode(droneId, 'AUTO') to begin flying the waypoints. Cancelling instead issues setMode(droneId, 'RTL').
The AUTO switch is a client-side decision, gated on live RELATIVE_ALT telemetry. A server-side helper _monitor_takeoff_and_switch_to_auto exists in the Gateway but is not called by start_mission. If the telemetry Socket.IO stream drops during climb-out, the confirmation dialog never appears and the vehicle holds in GUIDED at takeoff altitude — keep the telemetry client healthy while a mission is arming.

Rendering on the map (brief)

HomeComponent.renderMission (home.component.ts:1312) removes prior mission elements, draws the connecting flight-path line (renderMapLines), then adds a Threebox sphere per waypoint plus a dotted vertical ground line, skipping the coordinate-less commands [20, 178]. In AUTO mode, MissionService.updateCurrentMissionPointIndex advances the highlighted leg as the vehicle passes within 3 m of each point. The full 3D rendering pipeline (render order, Threebox layer, marker disposal) is documented in Map & Live Vehicle Tracking. Geofences share the map-click + MAVLink pattern but live in a sibling flow (EditGeofenceComponent, edit-geofence-point-dialog). Points use the ArduPilot fence commands from FENCE_COMMANDS (src/app/models/geofence.model.ts): 5001/5002 polygon vertex inclusion/exclusion and 5003/5004 circle inclusion/exclusion (param1 = radius). Circle geofences are limited to a single center point. They upload through DroneService.pushGeofence / syncGeofences / clearFence rather than push_mission. See Missions & Geofences API and Mission & Geofence MAVLink Format for the backend contract.

Gotchas to preserve

Command labels/icons/params are duplicated in edit-marker-dialog.component.ts (commandOptions + paramConfigs), edit-mission.component.ts (commandTypes / commandIcons), and the [20, 178] skip-lists in home.component.ts. Add a command in all of them or the sidebar, dialog, and map will disagree.
RTL (20) and Set Speed (178) have no valid lat/lng. They are excluded from waypoint spheres, ground lines, and the flight-path line, and their coordinate inputs are disabled in the dialog. Removing either guard renders a marker at (0,0) off the coast of Africa.
push_mission only transfers waypoints; start_mission re-uploads, arms, and takes off in GUIDED. They are separate endpoints and separate UI actions. The AUTO transition is client-driven via altitude telemetry, not part of start_mission.
If the first stored waypoint isn’t command 22, the Gateway prepends a single TAKEOFF. There is no auto-generated return path, RTL, or speed item despite older docs — the stored sequence is uploaded verbatim otherwise.
sequence orders the sidebar, the map line, and the upload. Reorders renumber it contiguously from 1; a gap or duplicate desyncs the views and can reorder the uploaded mission unexpectedly.