mavpilot 0.1.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.
- mavpilot-0.1.0/LICENSE +21 -0
- mavpilot-0.1.0/PKG-INFO +354 -0
- mavpilot-0.1.0/README.md +320 -0
- mavpilot-0.1.0/mavpilot/__init__.py +14 -0
- mavpilot-0.1.0/mavpilot/__main__.py +12 -0
- mavpilot-0.1.0/mavpilot/_viz.html +700 -0
- mavpilot-0.1.0/mavpilot/cli.py +205 -0
- mavpilot-0.1.0/mavpilot/constants.py +63 -0
- mavpilot-0.1.0/mavpilot/controller.py +1367 -0
- mavpilot-0.1.0/mavpilot/py.typed +0 -0
- mavpilot-0.1.0/mavpilot/types.py +76 -0
- mavpilot-0.1.0/mavpilot/utils.py +55 -0
- mavpilot-0.1.0/mavpilot/viz.py +155 -0
- mavpilot-0.1.0/mavpilot.egg-info/PKG-INFO +354 -0
- mavpilot-0.1.0/mavpilot.egg-info/SOURCES.txt +19 -0
- mavpilot-0.1.0/mavpilot.egg-info/dependency_links.txt +1 -0
- mavpilot-0.1.0/mavpilot.egg-info/entry_points.txt +2 -0
- mavpilot-0.1.0/mavpilot.egg-info/requires.txt +10 -0
- mavpilot-0.1.0/mavpilot.egg-info/top_level.txt +1 -0
- mavpilot-0.1.0/pyproject.toml +72 -0
- mavpilot-0.1.0/setup.cfg +4 -0
mavpilot-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Dmitry
|
|
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.
|
mavpilot-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: mavpilot
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Async PX4 drone controller — sequential autonomous flight via MAVLink
|
|
5
|
+
Author-email: Dmitry <Onikore@gmail.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Repository, https://github.com/Onikore/mavpilot
|
|
8
|
+
Project-URL: Issues, https://github.com/Onikore/mavpilot/issues
|
|
9
|
+
Keywords: drone,UAV,PX4,MAVLink,autonomous,offboard,robotics
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Intended Audience :: Science/Research
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Topic :: Scientific/Engineering
|
|
19
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
20
|
+
Classifier: Framework :: AsyncIO
|
|
21
|
+
Requires-Python: >=3.10
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
License-File: LICENSE
|
|
24
|
+
Requires-Dist: pymavlink
|
|
25
|
+
Provides-Extra: dev
|
|
26
|
+
Requires-Dist: pytest>=7.4; extra == "dev"
|
|
27
|
+
Requires-Dist: pytest-asyncio>=0.21; extra == "dev"
|
|
28
|
+
Requires-Dist: pytest-cov>=4.0; extra == "dev"
|
|
29
|
+
Requires-Dist: hypothesis>=6.0; extra == "dev"
|
|
30
|
+
Requires-Dist: black>=24.0; extra == "dev"
|
|
31
|
+
Requires-Dist: mypy>=1.7; extra == "dev"
|
|
32
|
+
Requires-Dist: ruff>=0.4; extra == "dev"
|
|
33
|
+
Dynamic: license-file
|
|
34
|
+
|
|
35
|
+
# mavpilot
|
|
36
|
+
|
|
37
|
+
> 🇬🇧 [English version](README_EN.md)
|
|
38
|
+
|
|
39
|
+
**Асинхронный контроллер PX4-дрона на Python** — последовательное автономное управление через MAVLink, встроенная 3D-визуализация в браузере и режим без железа.
|
|
40
|
+
|
|
41
|
+
[](https://github.com/Onikore/mavpilot/actions)
|
|
42
|
+
[](https://pypi.org/project/mavpilot/)
|
|
43
|
+
[](https://pypi.org/project/mavpilot/)
|
|
44
|
+
[](LICENSE)
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## Возможности
|
|
49
|
+
|
|
50
|
+
| | |
|
|
51
|
+
|---|---|
|
|
52
|
+
| **Чистый asyncio API** | Пишите последовательную логику миссии через `await` — без колбэков и машин состояний |
|
|
53
|
+
| **PX4 OFFBOARD режим** | Стримит `SET_POSITION_TARGET_LOCAL_NED` на частоте 50 Гц |
|
|
54
|
+
| **Точная посадка** | Визуально-направляемый спуск через простой callback API |
|
|
55
|
+
| **Движение в теле дрона** | `goto_body_relative()` без ручного пересчёта NED/курс |
|
|
56
|
+
| **Ограничение скорости рыскания** | Плавные переходы курса (по умолчанию 15 °/с, настраивается) |
|
|
57
|
+
| **Визуализация в браузере** | Живая 3D-траектория + телеметрия через HTTP+SSE — без npm, без CDN |
|
|
58
|
+
| **Mock-режим** | Встроенный физический симулятор — тестируйте миссию без SITL и железа |
|
|
59
|
+
| **Потокобезопасность** | Heartbeat, receiver и streamer крутятся в фоновых потоках |
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## Установка
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
pip install mavpilot
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Или из исходников:
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
git clone https://github.com/Onikore/mavpilot
|
|
73
|
+
cd mavpilot
|
|
74
|
+
pip install -e ".[dev]"
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
**Зависимость времени выполнения:** [pymavlink](https://pypi.org/project/pymavlink/) (устанавливается автоматически).
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## Быстрый старт — mock-режим
|
|
82
|
+
|
|
83
|
+
Дрон и SITL не нужны:
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
# Квадратная траектория
|
|
87
|
+
python -m mavpilot --mock
|
|
88
|
+
|
|
89
|
+
# Траектория в виде звезды
|
|
90
|
+
python -m mavpilot --mock --pattern star
|
|
91
|
+
|
|
92
|
+
# Демо точной посадки
|
|
93
|
+
python -m mavpilot --mock --precision-land
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Откройте **http://localhost:8765** в браузере — увидите живую 3D-визуализацию.
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
## Использование как библиотека
|
|
101
|
+
|
|
102
|
+
```python
|
|
103
|
+
import asyncio
|
|
104
|
+
from mavpilot import DroneController
|
|
105
|
+
|
|
106
|
+
async def mission():
|
|
107
|
+
drone = DroneController(
|
|
108
|
+
connection_string="udp:127.0.0.1:14540", # SITL по умолчанию
|
|
109
|
+
enable_viz=True, # визуализация в браузере на :8765
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
await drone.connect()
|
|
113
|
+
await drone.apply_safe_params() # рекомендуемые параметры безопасности PX4
|
|
114
|
+
await drone.wait_until_ready() # ждём EKF / LOCAL_POSITION_NED
|
|
115
|
+
|
|
116
|
+
await drone.takeoff(altitude_m=5.0)
|
|
117
|
+
|
|
118
|
+
# Координаты NED (x=Север, y=Восток, z=Вниз)
|
|
119
|
+
await drone.goto(x=10, y=0, z=-5)
|
|
120
|
+
await drone.goto(x=10, y=10, z=-5, yaw_deg=90)
|
|
121
|
+
await drone.goto_body_relative(forward_m=5, right_m=0, down_m=0)
|
|
122
|
+
await drone.hover(duration_s=3.0)
|
|
123
|
+
|
|
124
|
+
await drone.land()
|
|
125
|
+
drone.close()
|
|
126
|
+
|
|
127
|
+
asyncio.run(mission())
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### Точная посадка
|
|
131
|
+
|
|
132
|
+
Передайте callback, возвращающий смещение маркера в **системе координат тела дрона (FRD)**:
|
|
133
|
+
|
|
134
|
+
```python
|
|
135
|
+
from mavpilot import DroneController, MarkerObservation
|
|
136
|
+
|
|
137
|
+
def get_marker() -> MarkerObservation | None:
|
|
138
|
+
# подключите свой визуальный пайплайн
|
|
139
|
+
# dx = смещение вперёд (м), dy = смещение вправо (м)
|
|
140
|
+
return MarkerObservation(dx=0.3, dy=-0.1)
|
|
141
|
+
|
|
142
|
+
async def mission():
|
|
143
|
+
drone = DroneController(mock=True, enable_viz=False)
|
|
144
|
+
await drone.connect()
|
|
145
|
+
await drone.takeoff(altitude_m=10.0)
|
|
146
|
+
result = await drone.precision_land(
|
|
147
|
+
get_marker_offset=get_marker,
|
|
148
|
+
descent_rate_mps=0.3,
|
|
149
|
+
final_altitude_m=0.5,
|
|
150
|
+
horizontal_tolerance_m=0.15,
|
|
151
|
+
min_altitude_floor_m=0.3, # новый параметр в v0.2.0
|
|
152
|
+
)
|
|
153
|
+
if not result:
|
|
154
|
+
# status ∈ {ABORTED_AT_FLOOR, MARKER_LOST, TIMEOUT}
|
|
155
|
+
print(f"precision_land не приземлился: {result.status.value}")
|
|
156
|
+
print(f"финальная позиция: {result.final_position}")
|
|
157
|
+
drone.close()
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### Перевод пикселей камеры в смещение в теле дрона
|
|
161
|
+
|
|
162
|
+
```python
|
|
163
|
+
from mavpilot.utils import pixel_to_body_offset
|
|
164
|
+
|
|
165
|
+
dx, dy = pixel_to_body_offset(
|
|
166
|
+
px_norm_x=0.1, # нормализованные координаты [-1, 1]
|
|
167
|
+
px_norm_y=-0.05,
|
|
168
|
+
camera_hfov_deg=90.0,
|
|
169
|
+
camera_vfov_deg=60.0,
|
|
170
|
+
altitude_above_ground_m=drone.get_local_position().altitude,
|
|
171
|
+
camera_mount_yaw_deg=0.0,
|
|
172
|
+
)
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
---
|
|
176
|
+
|
|
177
|
+
## CLI
|
|
178
|
+
|
|
179
|
+
```
|
|
180
|
+
python -m mavpilot [ОПЦИИ]
|
|
181
|
+
|
|
182
|
+
Опции:
|
|
183
|
+
--connection STR MAVLink endpoint [по умолчанию: udp:127.0.0.1:14540]
|
|
184
|
+
--mock Симуляторный режим без железа
|
|
185
|
+
--viz-port INT Порт браузерной визуализации [по умолчанию: 8765]
|
|
186
|
+
--viz-host STR Интерфейс визуализатора [по умолчанию: 127.0.0.1]
|
|
187
|
+
Используйте 0.0.0.0 для доступа из локальной сети
|
|
188
|
+
--no-viz Отключить браузерную визуализацию
|
|
189
|
+
--precision-land Точная посадка с симулированным маркером
|
|
190
|
+
--pattern {square,star} Паттерн полёта в демо [по умолчанию: square]
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
### Поведение при ошибках и Ctrl-C
|
|
194
|
+
|
|
195
|
+
- **Ctrl-C** в любой момент миссии вызывает `emergency_land()`. Это включает: смену режима на `AUTO_LAND`, ожидание касания земли (до 10 с), отправку команды `MAV_CMD_NAV_LAND` если режим завис, и в крайнем случае `DO_FLIGHTTERMINATION` (мгновенное обесточивание моторов — дрон падает).
|
|
196
|
+
- **RTL не входит в `emergency_land()`**. Возврат на точку старта — это отдельная штатная операция (`drone.return_to_launch()`), не аварийная.
|
|
197
|
+
- Любое необработанное исключение в миссии (включая `KeyboardInterrupt`) также вызывает `emergency_land()`.
|
|
198
|
+
|
|
199
|
+
---
|
|
200
|
+
|
|
201
|
+
## Справочник API
|
|
202
|
+
|
|
203
|
+
### `DroneController(…)`
|
|
204
|
+
|
|
205
|
+
```python
|
|
206
|
+
DroneController(
|
|
207
|
+
connection_string = "udp:127.0.0.1:14540",
|
|
208
|
+
source_system = 255,
|
|
209
|
+
source_component = MAV_COMP_ID_MISSIONPLANNER,
|
|
210
|
+
loop_hz = 50.0, # частота стриминга сетпоинтов
|
|
211
|
+
enable_viz = True, # запустить браузерную визуализацию
|
|
212
|
+
viz_port = 8765,
|
|
213
|
+
mock = False, # симулятор без железа
|
|
214
|
+
yaw_slew_rate_deg = 15.0, # макс. скорость рыскания (°/с)
|
|
215
|
+
)
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
### Методы управления полётом
|
|
219
|
+
|
|
220
|
+
| Метод | Описание |
|
|
221
|
+
|---|---|
|
|
222
|
+
| `await connect(timeout_s)` | Открыть MAVLink и запустить фоновые потоки |
|
|
223
|
+
| `await apply_safe_params()` | Записать рекомендуемые параметры безопасности PX4 |
|
|
224
|
+
| `await wait_until_ready(timeout_s)` | Ждать пока EKF не выдаст LOCAL_POSITION_NED |
|
|
225
|
+
| `await takeoff(altitude_m, timeout_s)` | Арм, OFFBOARD режим, набор высоты |
|
|
226
|
+
| `await goto(x, y, z, yaw_deg, …)` | Лететь в точку NED |
|
|
227
|
+
| `await goto_relative(dx, dy, dz, …)` | Смещение от текущей позиции NED |
|
|
228
|
+
| `await goto_body_relative(fwd, right, down, …)` | Смещение в системе тела дрона |
|
|
229
|
+
| `await set_yaw(yaw_deg, timeout_s)` | Разворот на месте |
|
|
230
|
+
| `await hover(duration_s)` | Удерживать позицию |
|
|
231
|
+
| `await land(timeout_s)` | AUTO_LAND, ждать приземления |
|
|
232
|
+
| `await precision_land(callback, …)` | Визуально-направляемый спуск; возвращает `PrecisionLandResult` |
|
|
233
|
+
| `await return_to_launch(timeout_s)` | AUTO_RTL, ждать приземления |
|
|
234
|
+
| `await emergency_land()` | Цепочка: AUTO_LAND → NAV_LAND → DO_FLIGHTTERMINATION |
|
|
235
|
+
| `close()` | Остановить все потоки, закрыть соединение |
|
|
236
|
+
|
|
237
|
+
### Телеметрия
|
|
238
|
+
|
|
239
|
+
| Метод | Возвращает |
|
|
240
|
+
|---|---|
|
|
241
|
+
| `get_local_position()` | `Position(x, y, z)` в метрах NED |
|
|
242
|
+
| `get_yaw_rad()` / `get_yaw_deg()` | Текущий курс |
|
|
243
|
+
| `is_armed()` | `bool` |
|
|
244
|
+
| `is_offboard()` | `bool` |
|
|
245
|
+
| `landed_state()` | `int` (1 = на земле, 2 = в воздухе) |
|
|
246
|
+
|
|
247
|
+
### Датаклассы
|
|
248
|
+
|
|
249
|
+
```python
|
|
250
|
+
from mavpilot import Position, MarkerObservation
|
|
251
|
+
|
|
252
|
+
# Позиция в NED (x=Север, y=Восток, z=Вниз)
|
|
253
|
+
pos: Position # pos.altitude == -pos.z
|
|
254
|
+
|
|
255
|
+
# Смещение маркера в системе тела дрона FRD
|
|
256
|
+
obs: MarkerObservation # dx=вперёд, dy=вправо, dz=вниз (опционально)
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
---
|
|
260
|
+
|
|
261
|
+
## Система координат
|
|
262
|
+
|
|
263
|
+
mavpilot использует **NED-конвенцию PX4** из `LOCAL_POSITION_NED`:
|
|
264
|
+
|
|
265
|
+
| Ось | Направление | Примечание |
|
|
266
|
+
|---|---|---|
|
|
267
|
+
| x | Север (+) | |
|
|
268
|
+
| y | Восток (+) | |
|
|
269
|
+
| z | Вниз (+) | высота = `-z` |
|
|
270
|
+
|
|
271
|
+
Утилиты для преобразования координат:
|
|
272
|
+
|
|
273
|
+
```python
|
|
274
|
+
from mavpilot.utils import body_to_ned, ned_to_body, pixel_to_body_offset
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
---
|
|
278
|
+
|
|
279
|
+
## Визуализация
|
|
280
|
+
|
|
281
|
+
Лёгкий встроенный HTTP+SSE сервер раздаёт **3D-вид на Three.js** без сборки и пакетного менеджера. Откройте `http://localhost:8765` пока дрон работает.
|
|
282
|
+
|
|
283
|
+
Правая панель отображает:
|
|
284
|
+
- Статус арма и режим полёта
|
|
285
|
+
- Позицию, скорость, курс, заряд батареи в реальном времени
|
|
286
|
+
- Активный сетпоинт
|
|
287
|
+
- Лог команд (взлёт, goto, посадка, …)
|
|
288
|
+
- Сообщения PX4 STATUSTEXT
|
|
289
|
+
|
|
290
|
+
---
|
|
291
|
+
|
|
292
|
+
## Архитектура
|
|
293
|
+
|
|
294
|
+
```
|
|
295
|
+
asyncio event loop <-- ваш код миссии
|
|
296
|
+
|
|
|
297
|
+
v
|
|
298
|
+
DroneController
|
|
299
|
+
|
|
|
300
|
+
+-- heartbeat_thread (1 Гц MAVLink heartbeat)
|
|
301
|
+
+-- receiver_thread (разбор входящих MAVLink → self._tel)
|
|
302
|
+
+-- streamer_thread (публикация SET_POSITION_TARGET_LOCAL_NED @ 50 Гц)
|
|
303
|
+
+-- viz_server (опциональный HTTP+SSE → браузер)
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
Всё общее состояние защищено `_tel_lock` и `_setpoint_lock`. В коде миссии asyncio-примитивы не нужны.
|
|
307
|
+
|
|
308
|
+
---
|
|
309
|
+
|
|
310
|
+
## Подключение к реальному железу
|
|
311
|
+
|
|
312
|
+
```python
|
|
313
|
+
# UART (Raspberry Pi <-> Pixhawk)
|
|
314
|
+
drone = DroneController(connection_string="/dev/ttyAMA0")
|
|
315
|
+
|
|
316
|
+
# UDP (SITL или мост компаньон-компьютер → GCS)
|
|
317
|
+
drone = DroneController(connection_string="udp:192.168.1.10:14540")
|
|
318
|
+
|
|
319
|
+
# TCP
|
|
320
|
+
drone = DroneController(connection_string="tcp:127.0.0.1:5760")
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
**Рекомендуемые параметры безопасности** (устанавливаются через `apply_safe_params()`):
|
|
324
|
+
|
|
325
|
+
| Параметр | Значение | Назначение |
|
|
326
|
+
|---|---|---|
|
|
327
|
+
| `COM_RCL_EXCEPT` | 7 | Нет failsafe в offboard / mission / hold |
|
|
328
|
+
| `COM_OBL_RC_ACT` | 4 | Потеря RC → hold, не RTL |
|
|
329
|
+
| `COM_OF_LOSS_T` | 2.0 с | Таймаут потери offboard |
|
|
330
|
+
| `COM_RC_IN_MODE` | 1 | RC не требуется |
|
|
331
|
+
|
|
332
|
+
---
|
|
333
|
+
|
|
334
|
+
## Разработка
|
|
335
|
+
|
|
336
|
+
```bash
|
|
337
|
+
# Установка в editable-режиме с dev-зависимостями
|
|
338
|
+
pip install -e ".[dev]"
|
|
339
|
+
|
|
340
|
+
# Тесты
|
|
341
|
+
pytest -q
|
|
342
|
+
|
|
343
|
+
# Линтер
|
|
344
|
+
ruff check mavpilot/
|
|
345
|
+
|
|
346
|
+
# Проверка типов
|
|
347
|
+
mypy mavpilot/
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
---
|
|
351
|
+
|
|
352
|
+
## Лицензия
|
|
353
|
+
|
|
354
|
+
[MIT](LICENSE)
|
mavpilot-0.1.0/README.md
ADDED
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
# mavpilot
|
|
2
|
+
|
|
3
|
+
> 🇬🇧 [English version](README_EN.md)
|
|
4
|
+
|
|
5
|
+
**Асинхронный контроллер PX4-дрона на Python** — последовательное автономное управление через MAVLink, встроенная 3D-визуализация в браузере и режим без железа.
|
|
6
|
+
|
|
7
|
+
[](https://github.com/Onikore/mavpilot/actions)
|
|
8
|
+
[](https://pypi.org/project/mavpilot/)
|
|
9
|
+
[](https://pypi.org/project/mavpilot/)
|
|
10
|
+
[](LICENSE)
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## Возможности
|
|
15
|
+
|
|
16
|
+
| | |
|
|
17
|
+
|---|---|
|
|
18
|
+
| **Чистый asyncio API** | Пишите последовательную логику миссии через `await` — без колбэков и машин состояний |
|
|
19
|
+
| **PX4 OFFBOARD режим** | Стримит `SET_POSITION_TARGET_LOCAL_NED` на частоте 50 Гц |
|
|
20
|
+
| **Точная посадка** | Визуально-направляемый спуск через простой callback API |
|
|
21
|
+
| **Движение в теле дрона** | `goto_body_relative()` без ручного пересчёта NED/курс |
|
|
22
|
+
| **Ограничение скорости рыскания** | Плавные переходы курса (по умолчанию 15 °/с, настраивается) |
|
|
23
|
+
| **Визуализация в браузере** | Живая 3D-траектория + телеметрия через HTTP+SSE — без npm, без CDN |
|
|
24
|
+
| **Mock-режим** | Встроенный физический симулятор — тестируйте миссию без SITL и железа |
|
|
25
|
+
| **Потокобезопасность** | Heartbeat, receiver и streamer крутятся в фоновых потоках |
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## Установка
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
pip install mavpilot
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Или из исходников:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
git clone https://github.com/Onikore/mavpilot
|
|
39
|
+
cd mavpilot
|
|
40
|
+
pip install -e ".[dev]"
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
**Зависимость времени выполнения:** [pymavlink](https://pypi.org/project/pymavlink/) (устанавливается автоматически).
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## Быстрый старт — mock-режим
|
|
48
|
+
|
|
49
|
+
Дрон и SITL не нужны:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
# Квадратная траектория
|
|
53
|
+
python -m mavpilot --mock
|
|
54
|
+
|
|
55
|
+
# Траектория в виде звезды
|
|
56
|
+
python -m mavpilot --mock --pattern star
|
|
57
|
+
|
|
58
|
+
# Демо точной посадки
|
|
59
|
+
python -m mavpilot --mock --precision-land
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Откройте **http://localhost:8765** в браузере — увидите живую 3D-визуализацию.
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
## Использование как библиотека
|
|
67
|
+
|
|
68
|
+
```python
|
|
69
|
+
import asyncio
|
|
70
|
+
from mavpilot import DroneController
|
|
71
|
+
|
|
72
|
+
async def mission():
|
|
73
|
+
drone = DroneController(
|
|
74
|
+
connection_string="udp:127.0.0.1:14540", # SITL по умолчанию
|
|
75
|
+
enable_viz=True, # визуализация в браузере на :8765
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
await drone.connect()
|
|
79
|
+
await drone.apply_safe_params() # рекомендуемые параметры безопасности PX4
|
|
80
|
+
await drone.wait_until_ready() # ждём EKF / LOCAL_POSITION_NED
|
|
81
|
+
|
|
82
|
+
await drone.takeoff(altitude_m=5.0)
|
|
83
|
+
|
|
84
|
+
# Координаты NED (x=Север, y=Восток, z=Вниз)
|
|
85
|
+
await drone.goto(x=10, y=0, z=-5)
|
|
86
|
+
await drone.goto(x=10, y=10, z=-5, yaw_deg=90)
|
|
87
|
+
await drone.goto_body_relative(forward_m=5, right_m=0, down_m=0)
|
|
88
|
+
await drone.hover(duration_s=3.0)
|
|
89
|
+
|
|
90
|
+
await drone.land()
|
|
91
|
+
drone.close()
|
|
92
|
+
|
|
93
|
+
asyncio.run(mission())
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### Точная посадка
|
|
97
|
+
|
|
98
|
+
Передайте callback, возвращающий смещение маркера в **системе координат тела дрона (FRD)**:
|
|
99
|
+
|
|
100
|
+
```python
|
|
101
|
+
from mavpilot import DroneController, MarkerObservation
|
|
102
|
+
|
|
103
|
+
def get_marker() -> MarkerObservation | None:
|
|
104
|
+
# подключите свой визуальный пайплайн
|
|
105
|
+
# dx = смещение вперёд (м), dy = смещение вправо (м)
|
|
106
|
+
return MarkerObservation(dx=0.3, dy=-0.1)
|
|
107
|
+
|
|
108
|
+
async def mission():
|
|
109
|
+
drone = DroneController(mock=True, enable_viz=False)
|
|
110
|
+
await drone.connect()
|
|
111
|
+
await drone.takeoff(altitude_m=10.0)
|
|
112
|
+
result = await drone.precision_land(
|
|
113
|
+
get_marker_offset=get_marker,
|
|
114
|
+
descent_rate_mps=0.3,
|
|
115
|
+
final_altitude_m=0.5,
|
|
116
|
+
horizontal_tolerance_m=0.15,
|
|
117
|
+
min_altitude_floor_m=0.3, # новый параметр в v0.2.0
|
|
118
|
+
)
|
|
119
|
+
if not result:
|
|
120
|
+
# status ∈ {ABORTED_AT_FLOOR, MARKER_LOST, TIMEOUT}
|
|
121
|
+
print(f"precision_land не приземлился: {result.status.value}")
|
|
122
|
+
print(f"финальная позиция: {result.final_position}")
|
|
123
|
+
drone.close()
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### Перевод пикселей камеры в смещение в теле дрона
|
|
127
|
+
|
|
128
|
+
```python
|
|
129
|
+
from mavpilot.utils import pixel_to_body_offset
|
|
130
|
+
|
|
131
|
+
dx, dy = pixel_to_body_offset(
|
|
132
|
+
px_norm_x=0.1, # нормализованные координаты [-1, 1]
|
|
133
|
+
px_norm_y=-0.05,
|
|
134
|
+
camera_hfov_deg=90.0,
|
|
135
|
+
camera_vfov_deg=60.0,
|
|
136
|
+
altitude_above_ground_m=drone.get_local_position().altitude,
|
|
137
|
+
camera_mount_yaw_deg=0.0,
|
|
138
|
+
)
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
---
|
|
142
|
+
|
|
143
|
+
## CLI
|
|
144
|
+
|
|
145
|
+
```
|
|
146
|
+
python -m mavpilot [ОПЦИИ]
|
|
147
|
+
|
|
148
|
+
Опции:
|
|
149
|
+
--connection STR MAVLink endpoint [по умолчанию: udp:127.0.0.1:14540]
|
|
150
|
+
--mock Симуляторный режим без железа
|
|
151
|
+
--viz-port INT Порт браузерной визуализации [по умолчанию: 8765]
|
|
152
|
+
--viz-host STR Интерфейс визуализатора [по умолчанию: 127.0.0.1]
|
|
153
|
+
Используйте 0.0.0.0 для доступа из локальной сети
|
|
154
|
+
--no-viz Отключить браузерную визуализацию
|
|
155
|
+
--precision-land Точная посадка с симулированным маркером
|
|
156
|
+
--pattern {square,star} Паттерн полёта в демо [по умолчанию: square]
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
### Поведение при ошибках и Ctrl-C
|
|
160
|
+
|
|
161
|
+
- **Ctrl-C** в любой момент миссии вызывает `emergency_land()`. Это включает: смену режима на `AUTO_LAND`, ожидание касания земли (до 10 с), отправку команды `MAV_CMD_NAV_LAND` если режим завис, и в крайнем случае `DO_FLIGHTTERMINATION` (мгновенное обесточивание моторов — дрон падает).
|
|
162
|
+
- **RTL не входит в `emergency_land()`**. Возврат на точку старта — это отдельная штатная операция (`drone.return_to_launch()`), не аварийная.
|
|
163
|
+
- Любое необработанное исключение в миссии (включая `KeyboardInterrupt`) также вызывает `emergency_land()`.
|
|
164
|
+
|
|
165
|
+
---
|
|
166
|
+
|
|
167
|
+
## Справочник API
|
|
168
|
+
|
|
169
|
+
### `DroneController(…)`
|
|
170
|
+
|
|
171
|
+
```python
|
|
172
|
+
DroneController(
|
|
173
|
+
connection_string = "udp:127.0.0.1:14540",
|
|
174
|
+
source_system = 255,
|
|
175
|
+
source_component = MAV_COMP_ID_MISSIONPLANNER,
|
|
176
|
+
loop_hz = 50.0, # частота стриминга сетпоинтов
|
|
177
|
+
enable_viz = True, # запустить браузерную визуализацию
|
|
178
|
+
viz_port = 8765,
|
|
179
|
+
mock = False, # симулятор без железа
|
|
180
|
+
yaw_slew_rate_deg = 15.0, # макс. скорость рыскания (°/с)
|
|
181
|
+
)
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
### Методы управления полётом
|
|
185
|
+
|
|
186
|
+
| Метод | Описание |
|
|
187
|
+
|---|---|
|
|
188
|
+
| `await connect(timeout_s)` | Открыть MAVLink и запустить фоновые потоки |
|
|
189
|
+
| `await apply_safe_params()` | Записать рекомендуемые параметры безопасности PX4 |
|
|
190
|
+
| `await wait_until_ready(timeout_s)` | Ждать пока EKF не выдаст LOCAL_POSITION_NED |
|
|
191
|
+
| `await takeoff(altitude_m, timeout_s)` | Арм, OFFBOARD режим, набор высоты |
|
|
192
|
+
| `await goto(x, y, z, yaw_deg, …)` | Лететь в точку NED |
|
|
193
|
+
| `await goto_relative(dx, dy, dz, …)` | Смещение от текущей позиции NED |
|
|
194
|
+
| `await goto_body_relative(fwd, right, down, …)` | Смещение в системе тела дрона |
|
|
195
|
+
| `await set_yaw(yaw_deg, timeout_s)` | Разворот на месте |
|
|
196
|
+
| `await hover(duration_s)` | Удерживать позицию |
|
|
197
|
+
| `await land(timeout_s)` | AUTO_LAND, ждать приземления |
|
|
198
|
+
| `await precision_land(callback, …)` | Визуально-направляемый спуск; возвращает `PrecisionLandResult` |
|
|
199
|
+
| `await return_to_launch(timeout_s)` | AUTO_RTL, ждать приземления |
|
|
200
|
+
| `await emergency_land()` | Цепочка: AUTO_LAND → NAV_LAND → DO_FLIGHTTERMINATION |
|
|
201
|
+
| `close()` | Остановить все потоки, закрыть соединение |
|
|
202
|
+
|
|
203
|
+
### Телеметрия
|
|
204
|
+
|
|
205
|
+
| Метод | Возвращает |
|
|
206
|
+
|---|---|
|
|
207
|
+
| `get_local_position()` | `Position(x, y, z)` в метрах NED |
|
|
208
|
+
| `get_yaw_rad()` / `get_yaw_deg()` | Текущий курс |
|
|
209
|
+
| `is_armed()` | `bool` |
|
|
210
|
+
| `is_offboard()` | `bool` |
|
|
211
|
+
| `landed_state()` | `int` (1 = на земле, 2 = в воздухе) |
|
|
212
|
+
|
|
213
|
+
### Датаклассы
|
|
214
|
+
|
|
215
|
+
```python
|
|
216
|
+
from mavpilot import Position, MarkerObservation
|
|
217
|
+
|
|
218
|
+
# Позиция в NED (x=Север, y=Восток, z=Вниз)
|
|
219
|
+
pos: Position # pos.altitude == -pos.z
|
|
220
|
+
|
|
221
|
+
# Смещение маркера в системе тела дрона FRD
|
|
222
|
+
obs: MarkerObservation # dx=вперёд, dy=вправо, dz=вниз (опционально)
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
---
|
|
226
|
+
|
|
227
|
+
## Система координат
|
|
228
|
+
|
|
229
|
+
mavpilot использует **NED-конвенцию PX4** из `LOCAL_POSITION_NED`:
|
|
230
|
+
|
|
231
|
+
| Ось | Направление | Примечание |
|
|
232
|
+
|---|---|---|
|
|
233
|
+
| x | Север (+) | |
|
|
234
|
+
| y | Восток (+) | |
|
|
235
|
+
| z | Вниз (+) | высота = `-z` |
|
|
236
|
+
|
|
237
|
+
Утилиты для преобразования координат:
|
|
238
|
+
|
|
239
|
+
```python
|
|
240
|
+
from mavpilot.utils import body_to_ned, ned_to_body, pixel_to_body_offset
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
---
|
|
244
|
+
|
|
245
|
+
## Визуализация
|
|
246
|
+
|
|
247
|
+
Лёгкий встроенный HTTP+SSE сервер раздаёт **3D-вид на Three.js** без сборки и пакетного менеджера. Откройте `http://localhost:8765` пока дрон работает.
|
|
248
|
+
|
|
249
|
+
Правая панель отображает:
|
|
250
|
+
- Статус арма и режим полёта
|
|
251
|
+
- Позицию, скорость, курс, заряд батареи в реальном времени
|
|
252
|
+
- Активный сетпоинт
|
|
253
|
+
- Лог команд (взлёт, goto, посадка, …)
|
|
254
|
+
- Сообщения PX4 STATUSTEXT
|
|
255
|
+
|
|
256
|
+
---
|
|
257
|
+
|
|
258
|
+
## Архитектура
|
|
259
|
+
|
|
260
|
+
```
|
|
261
|
+
asyncio event loop <-- ваш код миссии
|
|
262
|
+
|
|
|
263
|
+
v
|
|
264
|
+
DroneController
|
|
265
|
+
|
|
|
266
|
+
+-- heartbeat_thread (1 Гц MAVLink heartbeat)
|
|
267
|
+
+-- receiver_thread (разбор входящих MAVLink → self._tel)
|
|
268
|
+
+-- streamer_thread (публикация SET_POSITION_TARGET_LOCAL_NED @ 50 Гц)
|
|
269
|
+
+-- viz_server (опциональный HTTP+SSE → браузер)
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
Всё общее состояние защищено `_tel_lock` и `_setpoint_lock`. В коде миссии asyncio-примитивы не нужны.
|
|
273
|
+
|
|
274
|
+
---
|
|
275
|
+
|
|
276
|
+
## Подключение к реальному железу
|
|
277
|
+
|
|
278
|
+
```python
|
|
279
|
+
# UART (Raspberry Pi <-> Pixhawk)
|
|
280
|
+
drone = DroneController(connection_string="/dev/ttyAMA0")
|
|
281
|
+
|
|
282
|
+
# UDP (SITL или мост компаньон-компьютер → GCS)
|
|
283
|
+
drone = DroneController(connection_string="udp:192.168.1.10:14540")
|
|
284
|
+
|
|
285
|
+
# TCP
|
|
286
|
+
drone = DroneController(connection_string="tcp:127.0.0.1:5760")
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
**Рекомендуемые параметры безопасности** (устанавливаются через `apply_safe_params()`):
|
|
290
|
+
|
|
291
|
+
| Параметр | Значение | Назначение |
|
|
292
|
+
|---|---|---|
|
|
293
|
+
| `COM_RCL_EXCEPT` | 7 | Нет failsafe в offboard / mission / hold |
|
|
294
|
+
| `COM_OBL_RC_ACT` | 4 | Потеря RC → hold, не RTL |
|
|
295
|
+
| `COM_OF_LOSS_T` | 2.0 с | Таймаут потери offboard |
|
|
296
|
+
| `COM_RC_IN_MODE` | 1 | RC не требуется |
|
|
297
|
+
|
|
298
|
+
---
|
|
299
|
+
|
|
300
|
+
## Разработка
|
|
301
|
+
|
|
302
|
+
```bash
|
|
303
|
+
# Установка в editable-режиме с dev-зависимостями
|
|
304
|
+
pip install -e ".[dev]"
|
|
305
|
+
|
|
306
|
+
# Тесты
|
|
307
|
+
pytest -q
|
|
308
|
+
|
|
309
|
+
# Линтер
|
|
310
|
+
ruff check mavpilot/
|
|
311
|
+
|
|
312
|
+
# Проверка типов
|
|
313
|
+
mypy mavpilot/
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
---
|
|
317
|
+
|
|
318
|
+
## Лицензия
|
|
319
|
+
|
|
320
|
+
[MIT](LICENSE)
|