Test against ArduPilot SITL
Most Mission Control recipes end with a test against a simulated autopilot. The SITL tool intools/sitl/ builds ArduPilot and bridges it to a WebSocket:
ws://[::1]:5760/?token=... URL it prints. In Mission Control, open the connect dialog, choose Flight Controller, then WebSocket, pick the SITL #1 preset and paste the full URL, token included. The token changes on every start.
Add a flight controller panel (Mission Control)
Configuration panels live undersrc/components/fc/<area>/. Every panel reads and writes parameters through usePanelParams, which works the same for MAVLink parameters and MSP virtual parameters.
1
Create the component
src/components/fc/safety/FailsafePanel.tsx is a complete example, including the unsaved-changes guard.2
List it in the panel navigation
Add an entry to
FC_NAV_ITEMS in src/components/drone-detail/fc-nav-items.tsx:requiredCapability hides the panel on firmwares that lack the capability. vehicleClasses, requiredVehicleType and excludeFirmware narrow it further.3
Route the id to the component
In
src/components/drone-detail/FcPanelRouter.tsx, add a dynamic() import for the panel and a case "my-new-panel": that returns it.4
Add the label
Add
"myNewPanel" under the fcNav namespace in locales/en.json. English is the source locale.5
Test with SITL
Connect to SITL, open the panel, and check that it loads, edits and writes the parameters.
Add a MAVLink decoder (Mission Control)
1
Register the message's CRC and length
In Without a CRC_EXTRA entry the parser cannot validate the frame.
src/lib/protocol/mavlink-crc-extra.ts, add the message id to both maps:2
Write the decoder
Add a typed
decode* function to the matching file in src/lib/protocol/messages/:3
Add the callback
Add a callback type in
src/lib/protocol/types/callbacks.ts, a myNewMsgCallbacks array to CallbackStore in src/lib/protocol/mavlink-adapter-callbacks.ts, and an onMyNewMsg entry in bindCallbackMethods. Declare onMyNewMsg?(callback: MyNewMsgCallback): () => void; on DroneProtocol in src/lib/protocol/types/protocol.ts. Making it optional means the MSP and mock adapters do not have to implement it.4
Handle and route the frame
Add a handler to the matching file in
src/lib/protocol/handlers/ that decodes the payload and calls each callback, then add a case to routeFrame() in src/lib/protocol/mavlink-adapter-frame-handlers.ts:5
Bridge it into a store
Subscribe in
bridgeTelemetry() in src/stores/drone-manager-bridge.ts and push the data into a store. Add the returned unsubscribe function to the array the bridge returns.Add a Zustand store (Mission Control)
1
Create the store
2
Select specific fields
persist with a storage name, a version and a migration:
version and branch on the old version in migrate whenever the stored shape changes.
If the store is fed by live telemetry, do not call set() per message. Mutate the data in place and notify through a coalesced version bumper, as telemetry-store does:
bumper.cancelVersionBump() from its clear() action.
Add an agent service (Drone Agent)
New long-running services are Rust binaries in thecrates/ workspace, installed to /opt/ados/bin/. The supervisor runs every unit through systemctl; it never spawns processes itself. The design is explained on Agent services.
1
Create the crate
Add
crates/ados-my-service/ with a binary target and add it to the workspace members in crates/Cargo.toml. Use ados-protocol for any socket you read or write, so framing and contract versions stay in one place.2
Write the unit file
Add Set
data/systemd/ados-my-service.service, following the existing units:MemoryMax, CPUQuota and ReadWritePaths to what the service really needs.3
Register it in the unit table
Add a Set
UnitSpec row to UNITS in crates/ados-protocol/src/units.rs. The supervisor’s catalog, the installer’s enable, start, teardown and health lists, the REST restart allowlist and the armed interlock’s link-dropping class are all derived from that table, and a test fails if a unit file in data/systemd/ has no row (or a row has no unit file):idle_ok: true if the service exits 0 on purpose when the node has nothing for it to do, and drops_link: true if restarting it drops a command, telemetry, video, RC, mesh, access-point or cloud link.4
Ship the binary
Add the binary to the installer’s prebuilt catalog in
crates/ados-installer/src/binaries.rs for the profiles that run it. CI publishes every service binary as an asset of the per-commit rev-<sha> release, and the installer fetches the assets for the active profile from there.Next steps
Contributing
Development setup, branches and pull requests.
Project structure
Where every module lives.
HAL and tiers
Add a board profile.