Documentation Index
Fetch the complete documentation index at: https://docs.altnautica.com/llms.txt
Use this file to discover all available pages before exploring further.
Mission planning is an extension point the GCS exposes to plugins. This page covers how to add pattern generators, read and write missions, and write to the geofence + rally surfaces.
Higher-level flight behaviors (Follow-Me, Orbit, thermal overlay, gimbal control, etc.) are scoped per-drone and ship through the extensions registry. See Multi-component plugins for the per-drone tab pattern those use.
Capabilities
| Capability | Lets the plugin… |
|---|
mission.read | Read the active mission and its items. |
mission.write | Append, replace, or modify mission items. |
mission.pattern.register | Register a custom pattern generator. |
mission.write is high risk; the operator sees an orange badge.
mission.pattern.register is medium risk because it affects what gets uploaded to the FC.
Pattern generators
Mission Control ships built-in patterns (grid survey, spiral
search, waypoint chain). A plugin adds new ones.
import { definePlugin, type PatternGenerator } from "@altnautica/plugin-sdk";
const concentricRings: PatternGenerator = {
id: "com.example.concentric-rings",
name: "Concentric rings",
description: "Inspection orbit at increasing radii.",
fields: [
{ id: "center", kind: "latlng", label: "Center" },
{ id: "minRadius", kind: "number", label: "Min radius (m)", default: 5 },
{ id: "maxRadius", kind: "number", label: "Max radius (m)", default: 30 },
{ id: "step", kind: "number", label: "Step (m)", default: 5 },
{ id: "altitude", kind: "number", label: "Altitude (m AGL)", default: 20 },
],
generate(input) {
const items = [];
for (let r = input.minRadius; r <= input.maxRadius; r += input.step) {
for (let theta = 0; theta < 360; theta += 30) {
items.push({
kind: "waypoint",
lat: offsetLat(input.center.lat, r, theta),
lng: offsetLng(input.center.lng, r, theta),
alt_agl_m: input.altitude,
});
}
}
return items;
},
};
definePlugin({
id: "com.example.inspection",
version: "0.1.0",
mount(ctx) {
ctx.mission.registerPattern(concentricRings);
return null;
},
});
The host renders the form from fields[], calls generate(input)
on submit, and offers the result as a preview the operator can
edit before upload.
fields[] field kinds:
| Kind | Renders as |
|---|
text | Text input |
number | Number input |
boolean | Toggle |
select | Dropdown with options[] |
latlng | Map-pinned point picker |
polygon | Map polygon drawing tool |
polyline | Map polyline drawing tool |
altitude | Altitude picker (AGL / MSL toggle) |
The host validates the form and rejects out-of-range numbers
before calling generate.
Reading and writing missions
// read
const mission = await ctx.mission.read("active");
console.log(mission.items.length);
// modify
const next = {
...mission,
items: [
...mission.items,
{ kind: "waypoint", lat: 12.97, lng: 77.59, alt_agl_m: 30 },
],
};
await ctx.mission.write("active", next);
The host validates the mission against the FC’s accepted item
types and rejects writes with schema_invalid if anything is
malformed. The validation rules are firmware-specific (ArduPilot
accepts different MAV_CMDs than Betaflight) and the host applies
the right rule set automatically.
Geofence and rally points
Plugins extend missions; they do not extend geofence directly.
Use the canonical mission.write with the geofence-specific
mission items:
await ctx.mission.write("geofence", {
items: [
{ kind: "fence_polygon_inclusion", points: [...] },
{ kind: "fence_circle_exclusion", center: {...}, radius_m: 50 },
],
});
The host enforces a fixed schema for fence items and rejects
unknown kinds.
Pattern preview
Patterns show a live preview on the map. The host
calls generate (or preview if your generator defines one)
every time the form changes. Patterns must therefore be cheap;
spending more than 100 ms per generate is a UX bug.
For expensive generation, ship a preview that returns a coarse
shape and a generate that does the full work:
{
preview(input) { return cheapPolygon(input); },
generate(input) { return expensiveWaypointList(input); },
}
Persisting custom mission types
If your generator needs to round-trip through the GCS’s
mission library, add a kind discriminator to your items and
register a serializer:
ctx.mission.registerItemKind("com.example.soil-sampling", {
serialize(item) { return { ...item, version: 1 }; },
deserialize(blob) { return blob; },
});
The host stores serialized items in the mission row in Convex and
restores them on load. The agent half ignores unknown kinds when
uploading to the FC unless a handler is registered for them.
Testing
The host ships a mock mission store the SDK exposes for tests:
import { createMockMission } from "@altnautica/plugin-sdk/testing";
const mission = createMockMission();
await mission.write("active", { items: [...] });
const items = await mission.read("active");
expect(items.length).toBe(3);
See also