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 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.
@@ -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
+ [![CI](https://github.com/Onikore/mavpilot/actions/workflows/ci.yml/badge.svg)](https://github.com/Onikore/mavpilot/actions)
42
+ [![PyPI](https://img.shields.io/pypi/v/mavpilot)](https://pypi.org/project/mavpilot/)
43
+ [![Python](https://img.shields.io/pypi/pyversions/mavpilot)](https://pypi.org/project/mavpilot/)
44
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](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)
@@ -0,0 +1,320 @@
1
+ # mavpilot
2
+
3
+ > 🇬🇧 [English version](README_EN.md)
4
+
5
+ **Асинхронный контроллер PX4-дрона на Python** — последовательное автономное управление через MAVLink, встроенная 3D-визуализация в браузере и режим без железа.
6
+
7
+ [![CI](https://github.com/Onikore/mavpilot/actions/workflows/ci.yml/badge.svg)](https://github.com/Onikore/mavpilot/actions)
8
+ [![PyPI](https://img.shields.io/pypi/v/mavpilot)](https://pypi.org/project/mavpilot/)
9
+ [![Python](https://img.shields.io/pypi/pyversions/mavpilot)](https://pypi.org/project/mavpilot/)
10
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](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)