mavctl 0.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- mavctl-0.2.0/.github/workflows/publish.yml +78 -0
- mavctl-0.2.0/.gitignore +29 -0
- mavctl-0.2.0/AGENTS.md +61 -0
- mavctl-0.2.0/CLAUDE.md +1 -0
- mavctl-0.2.0/LICENSE +21 -0
- mavctl-0.2.0/PKG-INFO +225 -0
- mavctl-0.2.0/README.md +196 -0
- mavctl-0.2.0/docs/PUBLISHING.md +144 -0
- mavctl-0.2.0/docs/SITL_ACCEPTANCE.md +148 -0
- mavctl-0.2.0/docs/SITL_ACCEPTANCE_PHASE2.md +324 -0
- mavctl-0.2.0/pyproject.toml +86 -0
- mavctl-0.2.0/skills/mavctl-flight/SKILL.md +79 -0
- mavctl-0.2.0/skills/mavctl-flight/references/safety.md +110 -0
- mavctl-0.2.0/skills/mavctl-flight/references/troubleshooting.md +143 -0
- mavctl-0.2.0/skills/mavctl-flight/references/workflows.md +155 -0
- mavctl-0.2.0/src/mavctl/__init__.py +3 -0
- mavctl-0.2.0/src/mavctl/adapter/__init__.py +28 -0
- mavctl-0.2.0/src/mavctl/adapter/base.py +86 -0
- mavctl-0.2.0/src/mavctl/adapter/pymavlink_adapter.py +584 -0
- mavctl-0.2.0/src/mavctl/cli/__init__.py +10 -0
- mavctl-0.2.0/src/mavctl/cli/app.py +406 -0
- mavctl-0.2.0/src/mavctl/cli/render.py +56 -0
- mavctl-0.2.0/src/mavctl/daemon/__init__.py +19 -0
- mavctl-0.2.0/src/mavctl/daemon/__main__.py +41 -0
- mavctl-0.2.0/src/mavctl/daemon/client.py +80 -0
- mavctl-0.2.0/src/mavctl/daemon/guards.py +447 -0
- mavctl-0.2.0/src/mavctl/daemon/process.py +166 -0
- mavctl-0.2.0/src/mavctl/daemon/server.py +496 -0
- mavctl-0.2.0/src/mavctl/daemon/wire.py +30 -0
- mavctl-0.2.0/src/mavctl/models/__init__.py +33 -0
- mavctl-0.2.0/src/mavctl/models/commands.py +69 -0
- mavctl-0.2.0/src/mavctl/models/protocol.py +62 -0
- mavctl-0.2.0/src/mavctl/models/state.py +53 -0
- mavctl-0.2.0/src/mavctl/models/telemetry.py +41 -0
- mavctl-0.2.0/src/mavctl/paths.py +39 -0
- mavctl-0.2.0/tests/__init__.py +1 -0
- mavctl-0.2.0/tests/conftest.py +19 -0
- mavctl-0.2.0/tests/fakes.py +85 -0
- mavctl-0.2.0/tests/test_adapter.py +825 -0
- mavctl-0.2.0/tests/test_cli.py +240 -0
- mavctl-0.2.0/tests/test_guards.py +410 -0
- mavctl-0.2.0/tests/test_project_docs.py +222 -0
- mavctl-0.2.0/tests/test_server.py +507 -0
- mavctl-0.2.0/tests/test_sitl.py +126 -0
- mavctl-0.2.0/tests/test_skill_docs.py +222 -0
- mavctl-0.2.0/uv.lock +944 -0
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
# Publishing is restricted to formal PEP 440 release tags (vX.Y.Z).
|
|
4
|
+
# Milestone tags such as v0.2.0-phase2 are NEVER published: the broad "v*"
|
|
5
|
+
# trigger exists only so wrong tags fail loudly instead of silently doing
|
|
6
|
+
# nothing; the first step enforces the strict vX.Y.Z pattern.
|
|
7
|
+
#
|
|
8
|
+
# ONE-TIME MANUAL SETUP (required before the first real tag push):
|
|
9
|
+
# 1. On pypi.org, register a Trusted Publisher for this project:
|
|
10
|
+
# PyPI project name : mavctl
|
|
11
|
+
# Owner : LeaderOnePro
|
|
12
|
+
# Repository : mavctl
|
|
13
|
+
# Workflow filename : publish.yml
|
|
14
|
+
# Environment : leave empty (no environment is used here);
|
|
15
|
+
# if you ever add `environment:` below, the name
|
|
16
|
+
# on PyPI must match exactly.
|
|
17
|
+
# 2. Rehearse on TestPyPI first and validate dist/ locally — see
|
|
18
|
+
# docs/PUBLISHING.md before cutting the first real vX.Y.Z tag.
|
|
19
|
+
#
|
|
20
|
+
# This workflow uses OIDC Trusted Publishing only; it never requires or reads
|
|
21
|
+
# a PyPI token secret.
|
|
22
|
+
|
|
23
|
+
on:
|
|
24
|
+
push:
|
|
25
|
+
tags:
|
|
26
|
+
- "v*"
|
|
27
|
+
|
|
28
|
+
permissions:
|
|
29
|
+
contents: read
|
|
30
|
+
|
|
31
|
+
jobs:
|
|
32
|
+
publish:
|
|
33
|
+
runs-on: ubuntu-latest
|
|
34
|
+
permissions:
|
|
35
|
+
id-token: write # OIDC identity for PyPI Trusted Publishing
|
|
36
|
+
steps:
|
|
37
|
+
- uses: actions/checkout@v4
|
|
38
|
+
|
|
39
|
+
- name: Enforce PEP 440 release tag (vX.Y.Z)
|
|
40
|
+
env:
|
|
41
|
+
RELEASE_TAG_PATTERN: '^v[0-9]+\.[0-9]+\.[0-9]+$'
|
|
42
|
+
run: |
|
|
43
|
+
if [[ ! "${GITHUB_REF_NAME}" =~ ${RELEASE_TAG_PATTERN} ]]; then
|
|
44
|
+
echo "::error::'${GITHUB_REF_NAME}' is not a formal release tag." \
|
|
45
|
+
"Only vX.Y.Z (e.g. v0.2.0) publishes to PyPI." \
|
|
46
|
+
"Milestone tags like v0.2.0-phase2 are never published."
|
|
47
|
+
exit 1
|
|
48
|
+
fi
|
|
49
|
+
|
|
50
|
+
- name: Install uv
|
|
51
|
+
uses: astral-sh/setup-uv@v5
|
|
52
|
+
|
|
53
|
+
- name: Install dependencies
|
|
54
|
+
run: uv sync
|
|
55
|
+
|
|
56
|
+
- name: Lint (ruff)
|
|
57
|
+
run: uv run ruff check .
|
|
58
|
+
|
|
59
|
+
- name: Type-check (mypy)
|
|
60
|
+
run: uv run mypy .
|
|
61
|
+
|
|
62
|
+
- name: Test (non-SITL)
|
|
63
|
+
run: uv run pytest -m "not sitl"
|
|
64
|
+
|
|
65
|
+
- name: Build distributions
|
|
66
|
+
run: uv build
|
|
67
|
+
|
|
68
|
+
- name: Verify tag matches package version
|
|
69
|
+
run: |
|
|
70
|
+
expected="v$(grep -m1 '^version' pyproject.toml | cut -d '"' -f2)"
|
|
71
|
+
if [[ "${GITHUB_REF_NAME}" != "${expected}" ]]; then
|
|
72
|
+
echo "::error::Tag ${GITHUB_REF_NAME} != package version ${expected}." \
|
|
73
|
+
"Land a release commit setting version = \"${GITHUB_REF_NAME#v}\" first."
|
|
74
|
+
exit 1
|
|
75
|
+
fi
|
|
76
|
+
|
|
77
|
+
- name: Publish to PyPI
|
|
78
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
mavctl-0.2.0/.gitignore
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*.egg-info/
|
|
5
|
+
.eggs/
|
|
6
|
+
build/
|
|
7
|
+
dist/
|
|
8
|
+
*.egg
|
|
9
|
+
|
|
10
|
+
# Virtual envs / uv
|
|
11
|
+
.venv/
|
|
12
|
+
venv/
|
|
13
|
+
uv.lock.bak
|
|
14
|
+
|
|
15
|
+
# Tooling caches
|
|
16
|
+
.mypy_cache/
|
|
17
|
+
.ruff_cache/
|
|
18
|
+
.pytest_cache/
|
|
19
|
+
.coverage
|
|
20
|
+
htmlcov/
|
|
21
|
+
|
|
22
|
+
# mavctl runtime state
|
|
23
|
+
.mavctl/
|
|
24
|
+
|
|
25
|
+
# OS / editor
|
|
26
|
+
.DS_Store
|
|
27
|
+
*.swp
|
|
28
|
+
.idea/
|
|
29
|
+
.vscode/
|
mavctl-0.2.0/AGENTS.md
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# mavctl
|
|
2
|
+
|
|
3
|
+
Headless ground control station CLI for MAVLink vehicles (ArduPilot-first),
|
|
4
|
+
designed to be driven by AI coding agents (Claude Code, Codex, etc.) as well as humans.
|
|
5
|
+
|
|
6
|
+
## 架构(不可违背的核心决策)
|
|
7
|
+
|
|
8
|
+
三层架构,依赖方向严格单向:CLI → Daemon → Adapter
|
|
9
|
+
|
|
10
|
+
1. **CLI 层**(`mavctl/cli/`):Typer 实现。薄壳,只做参数解析、
|
|
11
|
+
调用 daemon、格式化输出。不含任何业务逻辑。
|
|
12
|
+
2. **Daemon 层**(`mavctl/daemon/`):常驻进程,维持 MAVLink 连接、
|
|
13
|
+
缓存遥测状态、执行安全护栏检查。通过 Unix domain socket
|
|
14
|
+
(JSON-RPC 风格)与 CLI 通信。
|
|
15
|
+
3. **Adapter 层**(`mavctl/adapter/`):pymavlink 的唯一使用场所。
|
|
16
|
+
pymavlink 的 import 不得出现在其他任何层。
|
|
17
|
+
|
|
18
|
+
## 技术栈
|
|
19
|
+
|
|
20
|
+
- Python >= 3.10,包管理用 uv,构建配置 pyproject.toml
|
|
21
|
+
- CLI: typer | 数据模型: pydantic v2 | MAVLink: pymavlink
|
|
22
|
+
- 测试: pytest + pytest-asyncio | Lint: ruff | 类型: mypy(strict)
|
|
23
|
+
- Daemon 内部用 asyncio;pymavlink 的阻塞调用包在 executor 里
|
|
24
|
+
|
|
25
|
+
## 铁律
|
|
26
|
+
|
|
27
|
+
- **所有命令支持 `--json`**:输出结构化 JSON 到 stdout;人类可读格式为默认
|
|
28
|
+
- **退出码语义**:0=成功 1=通用错误 2=参数错误 3=daemon 未运行
|
|
29
|
+
4=飞控未连接 5=安全护栏拒绝 6=飞控 NACK/超时
|
|
30
|
+
- **安全护栏**:arm / takeoff / mode / rtl 等改变载具状态的命令
|
|
31
|
+
必须要求 `--confirm` 标志,否则拒绝执行(退出码 5)并说明原因
|
|
32
|
+
- **危险命令支持 `--dry-run`**
|
|
33
|
+
- 错误信息输出到 stderr,JSON 模式下为 `{"error": {...}}`
|
|
34
|
+
- 每个公共函数写类型注解;不写无意义的注释
|
|
35
|
+
|
|
36
|
+
## 设计原则(Phase 2 起贯彻,与铁律同等约束力)
|
|
37
|
+
|
|
38
|
+
- **异步操作语义**:所有会改变载具状态且需要时间完成的命令
|
|
39
|
+
(takeoff / land / rtl / mode 等)必须支持两种模式:
|
|
40
|
+
- 默认:发出指令并确认飞控 ACK 后立即返回
|
|
41
|
+
- `--wait`:阻塞直到目标状态达成(如到达目标高度、完成降落),
|
|
42
|
+
配套 `--timeout <秒>`(默认 60),超时返回退出码 6
|
|
43
|
+
- **status 输出自描述**:`mavctl status` 的输出必须让 agent 单次查询
|
|
44
|
+
就能重建对载具的完整认知,至少包含:connected、armed、mode、
|
|
45
|
+
system_status、relative_alt、landed_state(若可用)、battery、
|
|
46
|
+
gps_fix、home_position(若已设置)
|
|
47
|
+
- **幂等友好**:重复执行已达成的状态变更应返回成功而非报错
|
|
48
|
+
(如对已 armed 的载具执行 arm,返回 exit 0 并在输出中标注
|
|
49
|
+
"already armed"),确保 agent 重试时不被绊倒
|
|
50
|
+
|
|
51
|
+
## 测试要求
|
|
52
|
+
|
|
53
|
+
- Adapter 层:用 mock MAVLink 连接做单元测试
|
|
54
|
+
- 集成测试:标记 `@pytest.mark.sitl`,需要本地 ArduPilot SITL
|
|
55
|
+
(默认 udp:127.0.0.1:14550),CI 中可跳过
|
|
56
|
+
- 每次修改后运行:`ruff check . && mypy . && pytest -m "not sitl"`
|
|
57
|
+
|
|
58
|
+
## 常用命令
|
|
59
|
+
|
|
60
|
+
- 安装依赖:`uv sync`
|
|
61
|
+
- 启动 SITL(若已安装):`sim_vehicle.py -v ArduCopter --out udp:127.0.0.1:14550`
|
mavctl-0.2.0/CLAUDE.md
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
@AGENTS.md
|
mavctl-0.2.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 LeaderOnePro
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
mavctl-0.2.0/PKG-INFO
ADDED
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: mavctl
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Headless, agent-first MAVLink ground-control CLI for ArduPilot vehicles
|
|
5
|
+
Project-URL: Homepage, https://github.com/LeaderOnePro/mavctl
|
|
6
|
+
Project-URL: Repository, https://github.com/LeaderOnePro/mavctl
|
|
7
|
+
Project-URL: Issues, https://github.com/LeaderOnePro/mavctl/issues
|
|
8
|
+
Project-URL: Documentation, https://github.com/LeaderOnePro/mavctl/tree/main/docs
|
|
9
|
+
Author: LeaderOnePro
|
|
10
|
+
License-Expression: MIT
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Operating System :: MacOS :: MacOS X
|
|
15
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
22
|
+
Classifier: Topic :: Scientific/Engineering
|
|
23
|
+
Classifier: Topic :: System :: Networking
|
|
24
|
+
Requires-Python: >=3.10
|
|
25
|
+
Requires-Dist: pydantic>=2.6
|
|
26
|
+
Requires-Dist: pymavlink>=2.4.40
|
|
27
|
+
Requires-Dist: typer>=0.12
|
|
28
|
+
Description-Content-Type: text/markdown
|
|
29
|
+
|
|
30
|
+
# mavctl
|
|
31
|
+
|
|
32
|
+
> Headless, agent-first MAVLink ground-control CLI for ArduPilot vehicles.
|
|
33
|
+
|
|
34
|
+
mavctl is **ArduPilot-first** and built to be driven by both humans on a
|
|
35
|
+
terminal and AI coding agents (Claude Code, Codex, OpenClaw, …). A resident
|
|
36
|
+
daemon keeps the MAVLink link alive and caches vehicle state; every CLI call
|
|
37
|
+
is one short, structured request to that daemon.
|
|
38
|
+
|
|
39
|
+
**Status:** developed and verified against ArduPilot SITL. It has not been
|
|
40
|
+
proven across the breadth of real MAVLink vehicles and is **not** presented as
|
|
41
|
+
ready for production flight on a real aircraft.
|
|
42
|
+
|
|
43
|
+
## Why mavctl
|
|
44
|
+
|
|
45
|
+
GUI ground stations such as Mission Planner or QGroundControl are excellent
|
|
46
|
+
for a human at the controls — and a poor interface for a shell script or an
|
|
47
|
+
LLM agent: clickable UIs, no stable exit codes, no machine-readable output.
|
|
48
|
+
|
|
49
|
+
mavctl takes the other side of that trade:
|
|
50
|
+
|
|
51
|
+
- the daemon owns the MAVLink connection and continuously caches telemetry,
|
|
52
|
+
so each command is quick and stateless;
|
|
53
|
+
- every command prints human-readable output by default and structured JSON
|
|
54
|
+
with `--json`;
|
|
55
|
+
- failures carry explicit exit codes (3 daemon down, 4 link lost, 5 guard
|
|
56
|
+
rejection, 6 vehicle NACK / timeout) instead of stack traces;
|
|
57
|
+
- dangerous operations pass safety guards before anything reaches the vehicle;
|
|
58
|
+
- mavctl embeds no LLM — it is designed to be *called* by agents such as
|
|
59
|
+
Claude Code, Codex or OpenClaw, or by plain bash.
|
|
60
|
+
|
|
61
|
+
## Current capabilities
|
|
62
|
+
|
|
63
|
+
Implemented commands — this is the complete list:
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
mavctl daemon start|stop|status
|
|
67
|
+
mavctl status
|
|
68
|
+
mavctl telemetry
|
|
69
|
+
mavctl arm
|
|
70
|
+
mavctl disarm
|
|
71
|
+
mavctl mode <MODE>
|
|
72
|
+
mavctl takeoff --alt <metres>
|
|
73
|
+
mavctl land
|
|
74
|
+
mavctl rtl
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Cross-cutting behaviour:
|
|
78
|
+
|
|
79
|
+
| Flag / behaviour | Meaning |
|
|
80
|
+
| ---------------- | ------- |
|
|
81
|
+
| `--json` | structured output on stdout; errors as `{"error": {...}}` on stderr |
|
|
82
|
+
| `--confirm` | required on every state-changing command; without it exit code 5 |
|
|
83
|
+
| `--dry-run` | run the exact same guards, never reach the vehicle |
|
|
84
|
+
| `--wait --timeout <s>` | block until the target state is reached (default 60 s) |
|
|
85
|
+
| idempotent repeats | re-applying an achieved change succeeds ("already armed") |
|
|
86
|
+
| transaction safety | ACK/NACK handling, serialized commands, link-loss abort |
|
|
87
|
+
|
|
88
|
+
Not implemented — current scope only, not a roadmap promise:
|
|
89
|
+
|
|
90
|
+
```text
|
|
91
|
+
Mission upload/download/start
|
|
92
|
+
Parameters
|
|
93
|
+
Geofence
|
|
94
|
+
Log download / analysis
|
|
95
|
+
Firmware flashing
|
|
96
|
+
Multi-vehicle orchestration
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## Quickstart with ArduPilot SITL
|
|
100
|
+
|
|
101
|
+
Requires Python >= 3.10 and [uv](https://docs.astral.sh/uv/). Always bring up
|
|
102
|
+
SITL first; do not point an agent-driven workflow at a real vehicle.
|
|
103
|
+
|
|
104
|
+
Terminal 1 — start ArduPilot SITL:
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
sim_vehicle.py -v ArduCopter --out udp:127.0.0.1:14550
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Terminal 2 — install from source and connect:
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
uv sync
|
|
114
|
+
uv run mavctl daemon start --connect udp:127.0.0.1:14550
|
|
115
|
+
uv run mavctl status --json
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Safe takeoff to 10 m and return to launch:
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
uv run mavctl mode GUIDED --confirm --wait
|
|
122
|
+
uv run mavctl arm --confirm
|
|
123
|
+
# Poll status --json until armed=true (the arm ACK can beat the heartbeat)
|
|
124
|
+
uv run mavctl takeoff --alt 10 --confirm --wait --timeout 45
|
|
125
|
+
uv run mavctl rtl --confirm --wait --timeout 120
|
|
126
|
+
uv run mavctl daemon stop
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Safety notes — read before pointing mavctl at anything that flies:
|
|
130
|
+
|
|
131
|
+
- Validate every workflow in SITL first; treat real-aircraft use as its own
|
|
132
|
+
review process.
|
|
133
|
+
- After `arm`, poll `status --json` until `armed=true` before takeoff: the
|
|
134
|
+
COMMAND_ACK can arrive about one heartbeat before reported state catches up.
|
|
135
|
+
- End flights with `rtl` / `land`, not `disarm`. Ordinary `disarm` requires
|
|
136
|
+
provable ground contact (`ground_state_unknown` otherwise).
|
|
137
|
+
- `disarm --force` is an emergency motor stop only — in flight it can cause a
|
|
138
|
+
crash.
|
|
139
|
+
- There is no `arm --force` anywhere in mavctl; pre-arm checks cannot be
|
|
140
|
+
bypassed.
|
|
141
|
+
|
|
142
|
+
## Installation
|
|
143
|
+
|
|
144
|
+
### From PyPI
|
|
145
|
+
|
|
146
|
+
mavctl's first production release (`0.2.0`) is being prepared. After the
|
|
147
|
+
PyPI release is published, install with:
|
|
148
|
+
|
|
149
|
+
```bash
|
|
150
|
+
uv tool install mavctl
|
|
151
|
+
uvx mavctl --help
|
|
152
|
+
pipx install mavctl
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
Until that release is published these commands have nothing to fetch from
|
|
156
|
+
production PyPI; TestPyPI rehearsal artifacts are not production releases.
|
|
157
|
+
Release status and the publishing runbook live in
|
|
158
|
+
[docs/PUBLISHING.md](docs/PUBLISHING.md).
|
|
159
|
+
|
|
160
|
+
### From source
|
|
161
|
+
|
|
162
|
+
For development from source, use `uv sync` and `uv run mavctl …`:
|
|
163
|
+
|
|
164
|
+
```bash
|
|
165
|
+
git clone https://github.com/LeaderOnePro/mavctl.git
|
|
166
|
+
cd mavctl
|
|
167
|
+
uv sync
|
|
168
|
+
uv run mavctl --help
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
## Agent Skill
|
|
172
|
+
|
|
173
|
+
The repository ships a portable agent Skill under `skills/mavctl-flight/`
|
|
174
|
+
(entrypoint plus workflows / safety / troubleshooting references). It is a
|
|
175
|
+
source asset of this repo, not an installed package.
|
|
176
|
+
|
|
177
|
+
To use it with an agent runtime, install or symlink this directory according
|
|
178
|
+
to that runtime's current Skill discovery convention.
|
|
179
|
+
|
|
180
|
+
Example for Claude Code, project-local to this repository:
|
|
181
|
+
|
|
182
|
+
```bash
|
|
183
|
+
mkdir -p .claude/skills
|
|
184
|
+
ln -s ../../skills/mavctl-flight .claude/skills/mavctl-flight
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
That is one concrete example, not a universal convention — runtimes differ.
|
|
188
|
+
|
|
189
|
+
## Safety model
|
|
190
|
+
|
|
191
|
+
Short version; full details in
|
|
192
|
+
[skills/mavctl-flight/references/safety.md](skills/mavctl-flight/references/safety.md):
|
|
193
|
+
|
|
194
|
+
- the daemon owns the vehicle link; CLI calls are short transactions;
|
|
195
|
+
- state-changing commands require `--confirm`; `--dry-run` previews decisions;
|
|
196
|
+
- exit 4 = no live vehicle state (link lost / heartbeat expired), including
|
|
197
|
+
mid-`--wait` loss (`link_lost_during_wait`);
|
|
198
|
+
- exit 5 = guard rejection with structured `reason` + `hint`;
|
|
199
|
+
- exit 6 = vehicle NACK / ACK timeout / wait timeout;
|
|
200
|
+
- force-arm does not exist at any layer (CLI option, RPC field, adapter verb);
|
|
201
|
+
- ordinary `disarm` needs positive ground evidence, else `ground_state_unknown`
|
|
202
|
+
(exit 5);
|
|
203
|
+
- after link loss, `status` reflects stale cache: `armed` renders unknown/n/a,
|
|
204
|
+
never silently disarmed.
|
|
205
|
+
|
|
206
|
+
## Development
|
|
207
|
+
|
|
208
|
+
```bash
|
|
209
|
+
uv sync
|
|
210
|
+
uv run ruff check .
|
|
211
|
+
uv run mypy .
|
|
212
|
+
uv run pytest -m "not sitl"
|
|
213
|
+
uv run pytest -m sitl # requires a running ArduPilot SITL
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
Further reading:
|
|
217
|
+
|
|
218
|
+
- [docs/SITL_ACCEPTANCE.md](docs/SITL_ACCEPTANCE.md)
|
|
219
|
+
- [docs/SITL_ACCEPTANCE_PHASE2.md](docs/SITL_ACCEPTANCE_PHASE2.md)
|
|
220
|
+
- [AGENTS.md](AGENTS.md) — architecture rules and contribution constraints
|
|
221
|
+
- [skills/mavctl-flight/SKILL.md](skills/mavctl-flight/SKILL.md) — agent-facing flight guidance
|
|
222
|
+
|
|
223
|
+
## License
|
|
224
|
+
|
|
225
|
+
[MIT](LICENSE) — Copyright (c) 2026 LeaderOnePro.
|
mavctl-0.2.0/README.md
ADDED
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
# mavctl
|
|
2
|
+
|
|
3
|
+
> Headless, agent-first MAVLink ground-control CLI for ArduPilot vehicles.
|
|
4
|
+
|
|
5
|
+
mavctl is **ArduPilot-first** and built to be driven by both humans on a
|
|
6
|
+
terminal and AI coding agents (Claude Code, Codex, OpenClaw, …). A resident
|
|
7
|
+
daemon keeps the MAVLink link alive and caches vehicle state; every CLI call
|
|
8
|
+
is one short, structured request to that daemon.
|
|
9
|
+
|
|
10
|
+
**Status:** developed and verified against ArduPilot SITL. It has not been
|
|
11
|
+
proven across the breadth of real MAVLink vehicles and is **not** presented as
|
|
12
|
+
ready for production flight on a real aircraft.
|
|
13
|
+
|
|
14
|
+
## Why mavctl
|
|
15
|
+
|
|
16
|
+
GUI ground stations such as Mission Planner or QGroundControl are excellent
|
|
17
|
+
for a human at the controls — and a poor interface for a shell script or an
|
|
18
|
+
LLM agent: clickable UIs, no stable exit codes, no machine-readable output.
|
|
19
|
+
|
|
20
|
+
mavctl takes the other side of that trade:
|
|
21
|
+
|
|
22
|
+
- the daemon owns the MAVLink connection and continuously caches telemetry,
|
|
23
|
+
so each command is quick and stateless;
|
|
24
|
+
- every command prints human-readable output by default and structured JSON
|
|
25
|
+
with `--json`;
|
|
26
|
+
- failures carry explicit exit codes (3 daemon down, 4 link lost, 5 guard
|
|
27
|
+
rejection, 6 vehicle NACK / timeout) instead of stack traces;
|
|
28
|
+
- dangerous operations pass safety guards before anything reaches the vehicle;
|
|
29
|
+
- mavctl embeds no LLM — it is designed to be *called* by agents such as
|
|
30
|
+
Claude Code, Codex or OpenClaw, or by plain bash.
|
|
31
|
+
|
|
32
|
+
## Current capabilities
|
|
33
|
+
|
|
34
|
+
Implemented commands — this is the complete list:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
mavctl daemon start|stop|status
|
|
38
|
+
mavctl status
|
|
39
|
+
mavctl telemetry
|
|
40
|
+
mavctl arm
|
|
41
|
+
mavctl disarm
|
|
42
|
+
mavctl mode <MODE>
|
|
43
|
+
mavctl takeoff --alt <metres>
|
|
44
|
+
mavctl land
|
|
45
|
+
mavctl rtl
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Cross-cutting behaviour:
|
|
49
|
+
|
|
50
|
+
| Flag / behaviour | Meaning |
|
|
51
|
+
| ---------------- | ------- |
|
|
52
|
+
| `--json` | structured output on stdout; errors as `{"error": {...}}` on stderr |
|
|
53
|
+
| `--confirm` | required on every state-changing command; without it exit code 5 |
|
|
54
|
+
| `--dry-run` | run the exact same guards, never reach the vehicle |
|
|
55
|
+
| `--wait --timeout <s>` | block until the target state is reached (default 60 s) |
|
|
56
|
+
| idempotent repeats | re-applying an achieved change succeeds ("already armed") |
|
|
57
|
+
| transaction safety | ACK/NACK handling, serialized commands, link-loss abort |
|
|
58
|
+
|
|
59
|
+
Not implemented — current scope only, not a roadmap promise:
|
|
60
|
+
|
|
61
|
+
```text
|
|
62
|
+
Mission upload/download/start
|
|
63
|
+
Parameters
|
|
64
|
+
Geofence
|
|
65
|
+
Log download / analysis
|
|
66
|
+
Firmware flashing
|
|
67
|
+
Multi-vehicle orchestration
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Quickstart with ArduPilot SITL
|
|
71
|
+
|
|
72
|
+
Requires Python >= 3.10 and [uv](https://docs.astral.sh/uv/). Always bring up
|
|
73
|
+
SITL first; do not point an agent-driven workflow at a real vehicle.
|
|
74
|
+
|
|
75
|
+
Terminal 1 — start ArduPilot SITL:
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
sim_vehicle.py -v ArduCopter --out udp:127.0.0.1:14550
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Terminal 2 — install from source and connect:
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
uv sync
|
|
85
|
+
uv run mavctl daemon start --connect udp:127.0.0.1:14550
|
|
86
|
+
uv run mavctl status --json
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Safe takeoff to 10 m and return to launch:
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
uv run mavctl mode GUIDED --confirm --wait
|
|
93
|
+
uv run mavctl arm --confirm
|
|
94
|
+
# Poll status --json until armed=true (the arm ACK can beat the heartbeat)
|
|
95
|
+
uv run mavctl takeoff --alt 10 --confirm --wait --timeout 45
|
|
96
|
+
uv run mavctl rtl --confirm --wait --timeout 120
|
|
97
|
+
uv run mavctl daemon stop
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Safety notes — read before pointing mavctl at anything that flies:
|
|
101
|
+
|
|
102
|
+
- Validate every workflow in SITL first; treat real-aircraft use as its own
|
|
103
|
+
review process.
|
|
104
|
+
- After `arm`, poll `status --json` until `armed=true` before takeoff: the
|
|
105
|
+
COMMAND_ACK can arrive about one heartbeat before reported state catches up.
|
|
106
|
+
- End flights with `rtl` / `land`, not `disarm`. Ordinary `disarm` requires
|
|
107
|
+
provable ground contact (`ground_state_unknown` otherwise).
|
|
108
|
+
- `disarm --force` is an emergency motor stop only — in flight it can cause a
|
|
109
|
+
crash.
|
|
110
|
+
- There is no `arm --force` anywhere in mavctl; pre-arm checks cannot be
|
|
111
|
+
bypassed.
|
|
112
|
+
|
|
113
|
+
## Installation
|
|
114
|
+
|
|
115
|
+
### From PyPI
|
|
116
|
+
|
|
117
|
+
mavctl's first production release (`0.2.0`) is being prepared. After the
|
|
118
|
+
PyPI release is published, install with:
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
uv tool install mavctl
|
|
122
|
+
uvx mavctl --help
|
|
123
|
+
pipx install mavctl
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Until that release is published these commands have nothing to fetch from
|
|
127
|
+
production PyPI; TestPyPI rehearsal artifacts are not production releases.
|
|
128
|
+
Release status and the publishing runbook live in
|
|
129
|
+
[docs/PUBLISHING.md](docs/PUBLISHING.md).
|
|
130
|
+
|
|
131
|
+
### From source
|
|
132
|
+
|
|
133
|
+
For development from source, use `uv sync` and `uv run mavctl …`:
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
git clone https://github.com/LeaderOnePro/mavctl.git
|
|
137
|
+
cd mavctl
|
|
138
|
+
uv sync
|
|
139
|
+
uv run mavctl --help
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
## Agent Skill
|
|
143
|
+
|
|
144
|
+
The repository ships a portable agent Skill under `skills/mavctl-flight/`
|
|
145
|
+
(entrypoint plus workflows / safety / troubleshooting references). It is a
|
|
146
|
+
source asset of this repo, not an installed package.
|
|
147
|
+
|
|
148
|
+
To use it with an agent runtime, install or symlink this directory according
|
|
149
|
+
to that runtime's current Skill discovery convention.
|
|
150
|
+
|
|
151
|
+
Example for Claude Code, project-local to this repository:
|
|
152
|
+
|
|
153
|
+
```bash
|
|
154
|
+
mkdir -p .claude/skills
|
|
155
|
+
ln -s ../../skills/mavctl-flight .claude/skills/mavctl-flight
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
That is one concrete example, not a universal convention — runtimes differ.
|
|
159
|
+
|
|
160
|
+
## Safety model
|
|
161
|
+
|
|
162
|
+
Short version; full details in
|
|
163
|
+
[skills/mavctl-flight/references/safety.md](skills/mavctl-flight/references/safety.md):
|
|
164
|
+
|
|
165
|
+
- the daemon owns the vehicle link; CLI calls are short transactions;
|
|
166
|
+
- state-changing commands require `--confirm`; `--dry-run` previews decisions;
|
|
167
|
+
- exit 4 = no live vehicle state (link lost / heartbeat expired), including
|
|
168
|
+
mid-`--wait` loss (`link_lost_during_wait`);
|
|
169
|
+
- exit 5 = guard rejection with structured `reason` + `hint`;
|
|
170
|
+
- exit 6 = vehicle NACK / ACK timeout / wait timeout;
|
|
171
|
+
- force-arm does not exist at any layer (CLI option, RPC field, adapter verb);
|
|
172
|
+
- ordinary `disarm` needs positive ground evidence, else `ground_state_unknown`
|
|
173
|
+
(exit 5);
|
|
174
|
+
- after link loss, `status` reflects stale cache: `armed` renders unknown/n/a,
|
|
175
|
+
never silently disarmed.
|
|
176
|
+
|
|
177
|
+
## Development
|
|
178
|
+
|
|
179
|
+
```bash
|
|
180
|
+
uv sync
|
|
181
|
+
uv run ruff check .
|
|
182
|
+
uv run mypy .
|
|
183
|
+
uv run pytest -m "not sitl"
|
|
184
|
+
uv run pytest -m sitl # requires a running ArduPilot SITL
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
Further reading:
|
|
188
|
+
|
|
189
|
+
- [docs/SITL_ACCEPTANCE.md](docs/SITL_ACCEPTANCE.md)
|
|
190
|
+
- [docs/SITL_ACCEPTANCE_PHASE2.md](docs/SITL_ACCEPTANCE_PHASE2.md)
|
|
191
|
+
- [AGENTS.md](AGENTS.md) — architecture rules and contribution constraints
|
|
192
|
+
- [skills/mavctl-flight/SKILL.md](skills/mavctl-flight/SKILL.md) — agent-facing flight guidance
|
|
193
|
+
|
|
194
|
+
## License
|
|
195
|
+
|
|
196
|
+
[MIT](LICENSE) — Copyright (c) 2026 LeaderOnePro.
|