Thank you for your interest in contributing to Altnautica Command — an open-source ground control station for ArduPilot and multi-protocol drone fleets.
Altnautica Command is licensed under the GNU General Public License v3.0. By contributing, you agree that your contributions will be licensed under GPLv3.
- Node.js 20+ — nodejs.org
- npm — bundled with Node.js
- Git
- Chrome 89+ — required for WebSerial (hardware connection). Firefox and Safari do not support WebSerial.
git clone https://github.com/altnautica/ADOSMissionControl.git
cd ADOSMissionControl
npm install
npm run demo # Start with 5 simulated drones (recommended for development)
npm run dev # Start without demo data
npm run build # Production build
npm run lint # Run ESLintOpen http://localhost:4000 in Chrome.
Demo mode (npm run demo) runs a full mock flight engine with 5 simulated drones, telemetry streams, and pre-arm check outputs. Use this for UI development — no hardware required.
ADOSMissionControl/
├── src/
│ ├── app/ # Next.js App Router pages
│ ├── components/
│ │ ├── air-traffic/ # Air traffic 3D viewer, panels, entities, overlays
│ │ ├── fc/ # Flight controller panels (CalibrationPanel, PidTuningPanel, etc.)
│ │ ├── indicators/ # Telemetry indicator widgets
│ │ ├── planner/ # Mission planner components
│ │ ├── map/ # Map components (Leaflet)
│ │ └── library/ # Shared UI primitives
│ ├── hooks/ # Custom React hooks
│ ├── lib/
│ │ ├── airspace/ # ADS-B providers, zone data, threat calculator
│ │ └── protocol/ # MAVLink parser, encoder, adapter, messages
│ └── stores/ # Zustand stores (33 domains)
├── public/
└── ...
- Strict mode is on — no
anytypes. Useunknownand narrow explicitly. - Explicit return types on all public APIs and exported functions.
- Prefer
interfacefor object shapes,typefor unions and aliases.
- Tailwind v4 — CSS-first configuration with design tokens.
- No arbitrary values (
w-[347px]) where a design token exists. Use the token. - Dark-first — all components must look correct on the dark theme.
- Zustand for all application state — 33 stores, one per domain.
- Ring buffers for telemetry data (telemetry-store) — fixed-size circular arrays to cap memory usage.
- Never use
useStatefor data that needs to be shared across components. Put it in a store. - Store files live in
src/stores/. One file per store.
DroneProtocolinterface is the only entry point for FC communication — never import MAVLink primitives directly from components or stores.- All panel writes go through
protocol.setParameter(). - ArduPilot auto-saves parameters to EEPROM on
PARAM_SET(confirmed fromGCS_Param.cpp). You do not need to callcommitParamsToFlash()for ArduPilot, but the call is retained as a belt-and-suspenders measure for non-ArduPilot firmware.
- Every FC panel uses the
usePanelParamshook to load parameters. - Split param lists into
requiredParamsandoptionalParams— panels must not fail to load when optional params are absent from the firmware. - Use
PanelHeader,PanelLoadingSkeleton, andPanelErrorStateshared components. - Wrap write operations in
DisconnectGuard(prevents writes when connection is lost) anduseArmedLock(prevents writes when vehicle is armed) where appropriate.
- Create
src/components/fc/YourPanel.tsx. - Use the
usePanelParamshook with separaterequiredParamsandoptionalParamsarrays:const { params, loading, error } = usePanelParams({ required: ['PARAM_ONE', 'PARAM_TWO'], optional: ['OPTIONAL_PARAM'], });
- Render
<PanelLoadingSkeleton />whileloading,<PanelErrorState />onerror. - Use
<PanelHeader title="Your Panel" />at the top. - Write parameters with
protocol.setParameter('PARAM_NAME', value). - Add your panel to the sidebar navigation in the FC configure tab.
- Wrap destructive or safety-critical writes with
<DisconnectGuard>and checkuseArmedLock.
- CRC_EXTRA — Add the message ID and CRC_EXTRA byte to the lookup table in
src/lib/protocol/mavlink-parser.ts. - Decode — Add a
decode<MessageName>()function insrc/lib/protocol/mavlink-messages.ts. Return a typed object matching the MAVLink field layout. - Dispatch — Add a
case MSG_ID:branch to the dispatch switch insrc/lib/protocol/mavlink-adapter.ts. Emit to the appropriate Zustand store or invoke a callback. - Encode (if sending) — Add an
encode<MessageName>()function insrc/lib/protocol/mavlink-encoder.ts. Follow the existing byte-packing pattern usingDataView.
Test by enabling the MAVLink Inspector panel in the UI — it shows all incoming raw messages.
- Create the component in
src/components/indicators/YourIndicator.tsx. - Read state from the appropriate Zustand store (
telemetry-store,sensor-health-store,diagnostics-store, etc.). Do not accept telemetry data as props. - Keep indicators stateless and reactive — they should re-render automatically when store state changes.
- Export from
src/components/indicators/index.ts.
- Fork the repo and create a feature branch from
main. Use a descriptive branch name:feat/rc-trim-panel,fix/mavlink-heartbeat-timeout. - Keep PRs focused — one feature or fix per PR. Large PRs are hard to review and slow to merge.
- Write a clear description: what changed, why it changed, and how to test it.
- Test with
npm run demoto verify your changes work against simulated drone data. - Test with real hardware if your changes touch the protocol layer, calibration flows, or any pre-arm check logic.
- Run
npm run build— ensure no TypeScript compilation errors. - Run
npm run lint— ensure no ESLint errors. - Do not bump
package.jsonversion yourself — maintainers handle releases.
Use GitHub Issues for bugs and feature requests.
For bug reports, include:
- Browser and version (must be Chrome 89+ for WebSerial features)
- Operating system
- Firmware type and version (ArduPilot, Betaflight, iNav) if hardware-related
- Steps to reproduce
- Expected vs actual behavior
- Screenshot or screen recording if the issue is visual
- MAVLink Inspector output if the issue is protocol-related
For feature requests, include:
- The use case you are trying to solve
- What firmware / hardware you are targeting
- Any reference implementations (QGC, Mission Planner, Betaflight Configurator)
By contributing to Altnautica Command, you agree that your contributions will be licensed under the GNU General Public License v3.0.