WaypointPush service items and dispatched over the rosbridge connection. This page documents that ORM → MAVLink translation, the one piece of logic the gateway injects (an auto-prepended TAKEOFF), and the fence command mapping — plus a stale documentation warning you must not trust.
This format is the shared contract between three surfaces: the Dashboard mission planner writes these fields, the gateway persists and translates them here, and the drone’s MAVROS consumes them. See Missions & Geofences API for the CRUD endpoints, Dashboard mission planning for the editor, and MAVLink routing for the on-drone side.
End-to-end upload flow
The MissionPoint model
src/models/mission_point.py carries both a semantic classification (type) used by the UI and the full MAVLink waypoint payload. A single row is one waypoint.
| Column | Type | Default | Role |
|---|---|---|---|
lat, lng, altitude | Float (NOT NULL) | — | Geographic position; become x_lat / y_long / z_alt |
type | Enum base / fly / safepoint | — | Semantic label (UI only, not sent to MAVLink) |
label | String(255) | — | Human-readable name |
sequence | Integer (NOT NULL) | — | Upload order key — points are always loaded ORDER BY sequence ASC |
frame | Integer | 3 | MAVLink frame (0=GLOBAL absolute alt, 3=GLOBAL_RELATIVE_ALT) |
command | Integer | 16 | MAVLink command (see table below) |
is_current | Boolean | False | MAVLink “current waypoint” flag |
autocontinue | Boolean | True | Continue to next waypoint automatically |
param1-param4 | Float | 0.0 | Command-specific parameters |
mission_id and user_id are both ON DELETE CASCADE FKs. The parent Mission (src/models/mission.py) is just a header (user_id, name, unique on uq_user_name) with a mission_points relationship. Full schema context lives in Database Schema Overview.
MAVLink command / frame reference
These are the command codes the fields hold. Only16 and 22 are produced automatically by the gateway; the rest come from whatever the Dashboard persisted.
| Command | Constant | Meaning |
|---|---|---|
16 | MAV_CMD_NAV_WAYPOINT | Fly to point (default for stored waypoints) |
22 | MAV_CMD_NAV_TAKEOFF | Take off (auto-prepended, see below) |
21 | MAV_CMD_NAV_LAND | Land |
20 | MAV_CMD_NAV_RETURN_TO_LAUNCH | RTL |
178 | MAV_CMD_DO_CHANGE_SPEED | Speed change |
| Frame | Meaning |
|---|---|
0 | GLOBAL (absolute MSL altitude) |
3 | GLOBAL_RELATIVE_ALT (altitude relative to home) |
to_mavlink_waypoint() conversion
Each row emits exactly one rosbridge waypoint dict via MissionPoint.to_mavlink_waypoint() (src/models/mission_point.py:59):
src/models/mission_point.py
| ORM column | MAVLink key |
|---|---|
lat | x_lat |
lng | y_long |
altitude | z_alt |
frame, command, is_current, autocontinue, param1-param4 | same names |
push_mission() — the auto-TAKEOFF prepend
DroneControlService.push_mission() (src/service/drone_control_service.py:784) is the only place the gateway adds logic on top of the stored rows. It is invoked from POST /api/v1/drone/action/push_mission and internally by start_mission().
Load & convert
mission_service.get_mission_points(mission_id, user_id) returns rows ordered by sequence; each is mapped through to_mavlink_waypoint().Prepend TAKEOFF if missing
If the list is non-empty and the first command is not
22, a synthetic TAKEOFF is inserted at index 0: frame=3, command=22, x_lat=0.0, y_long=0.0 (ArduPilot takes off from current position when lat/lng are 0). The takeoff altitude is custom_takeoff_altitude if provided, otherwise the first waypoint’s z_alt (falling back to 10.0).Push over rosbridge
Sends a
call_service frame to PUSH_MISSION = ("/mavros/mission/push", "mavros_msgs/srv/WaypointPush") with {"start_index": 0, "waypoints": [...]}, waiting up to ROSBRIDGE_SERVICE_TIMEOUT (30s) for a response.Verify transfer count
Requires
response["success"] and values["wp_transfered"] == len(mavlink_waypoints); otherwise raises Mission upload incomplete.src/service/drone_control_service.py
wp_transfered is spelled with one r — that is the actual field name in the MAVROS WaypointPush response, not a typo in this codebase. Do not “correct” it.Relationship to start_mission
POST /api/v1/drone/action/start_mission {drone_id, takeoff_altitude?} calls push_mission() again (re-uploading the drone’s assigned mission), then sets GUIDED mode, arms, and issues a GUIDED takeoff. It does not switch the vehicle to AUTO; the Dashboard monitors relative altitude and performs the AUTO switch. A server-side helper _monitor_takeoff_and_switch_to_auto() (src/service/drone_control_service.py:676) exists for this but is not invoked by start_mission(). See DroneControlService & Rosbridge Dispatch for the command layer.
Geofences: fence commands 5001-5004
Geofences reuse the same MAVLink item shape but with fence commands instead of nav commands. AGeofence (src/models/geofence.py) is type (polygon / circle) × fence_type (inclusion / exclusion, default exclusion) with cascade-deleted GeofencePoint children.
src/models/geofence_point.py defines the command mapping:
| Command | Constant | Shape | param1 |
|---|---|---|---|
5001 | MAV_CMD_NAV_FENCE_POLYGON_VERTEX_INCLUSION | Polygon vertex (stay inside) | vertex count |
5002 | MAV_CMD_NAV_FENCE_POLYGON_VERTEX_EXCLUSION | Polygon vertex (stay outside) | vertex count |
5003 | MAV_CMD_NAV_FENCE_CIRCLE_INCLUSION | Circle center (stay inside) | radius (m) |
5004 | MAV_CMD_NAV_FENCE_CIRCLE_EXCLUSION | Circle center (stay outside) | radius (m) |
get_fence_command(geofence_type, fence_type) selects the code from the type × fence_type pair. Each vertex/center is a GeofencePoint (lat, lng, sequence, frame default 3, command NOT NULL, param1-param4).
GeofencePoint.to_mavlink_fence_item() (src/models/geofence_point.py:66) mirrors to_mavlink_waypoint() but forces z_alt = 0.0 because fences are 2D:
src/models/geofence_point.py
push_geofence() and friends
DroneControlService.push_geofence() (src/service/drone_control_service.py:866) loads the geofence with points, computes the command via get_fence_command(geofence.type, geofence.fence_type or "exclusion"), then overrides every item’s command with that value. For polygons it also overwrites param1 with the total vertex count (len(fence_items)); circles keep the stored radius. It pushes via a dedicated service:
src/utils/mavros_topics.py
push_mission: response.success and wp_transfered == len(fence_items).
| Method | Route | Behavior |
|---|---|---|
push_geofence | POST /api/v1/drone/action/push_geofence {drone_id, geofence_id} | Upload one geofence’s points as a fence |
clear_fence | POST /api/v1/drone/action/clear_fence {drone_id} | Push an empty waypoints: [] to PUSH_FENCE |
sync_geofences | POST /api/v1/drone/action/sync_geofences {drone_id} | Upload all enabled geofences as one combined fence list |
Endpoint summary
All actions arePOST under /api/v1, JWT-protected (@jwt_required()), defined in src/routes/drone_routes.py.
| Endpoint | Body | Service method |
|---|---|---|
/drone/action/push_mission | {drone_id, mission_id} | push_mission (:784) |
/drone/action/start_mission | {drone_id, takeoff_altitude?} | start_mission (:615) |
/drone/action/push_geofence | {drone_id, geofence_id} | push_geofence (:866) |
/drone/action/clear_fence | {drone_id} | clear_fence (:936) |
/drone/action/sync_geofences | {drone_id} | sync_geofences (:959) |
Missions & Geofences API
CRUD endpoints that populate these tables.
DroneControlService
How
call_service frames reach the drone over rosbridge.Rosbridge Connection
The
send_service_call_with_response transport and timeouts.Schema Overview
Full column list and cascade rules for these tables.

