k16. Протоколы и интерфейсы: слои связи «для дронов»
UAV Python Pro · Конспект K16 · Модуль M3. Протоколы и интерфейсы: слои связи «для дронов» · Редакция 1.0 · После CHECKPOINT-K15 · Модуль M2 закрыт
Модуль M2 завершён: вы умеете вычислять расстояния, направления и ориентацию — математический язык мира дронов освоен. Но числа должны дойти от аппарата до земли и обратно, и на этом пути работает целая цепочка соглашений, невидимая до тех пор, пока что-нибудь не сломается. Этим уроком начинается модуль M3 «Протоколы и интерфейсы». Его первый конспект отвечает на три вопроса: из каких слоёв состоит связь, где среди них живёт MAVLink и какие характеристики канала — задержка, джиттер, потери, пропускная способность — обязан контролировать программист наземной станции.
Протокол — это соглашение между двумя программами о том, как понимать передаваемые байты. Соглашение устроено слоями: физический уровень передаёт биты по проводу или радиоволне, транспорт доставляет блоки байтов, кадр выделяет сообщения внутри потока и проверяет их целостность контрольной суммой, сообщение несёт осмысленные поля, а приложение решает, что делать с полученным. MAVLink занимает ровно два средних слоя — кадр и сообщение — и не заботится о том, что течёт под ним: serial-поток, UDP-датаграммы или USB. Понимать эту структуру означает уметь быстро определить, на каком именно уровне возникла проблема со связью.
Содержание
- 1. Цели урока
- 2. Словарь урока
- 3. Зачем нужна слоёная модель: диагностика связи
- 4. Пять слоёв связи «для дронов»
- 5. Физический уровень и транспорт в мире БПЛА
- 6. Кадр: границы сообщения и целостность
- 7. Сообщение: идентификатор и поля
- 8. Приложение: кто потребляет сообщения
- 9. Качество канала: задержка, джиттер, потери, пропускная способность
- 10. Где живёт MAVLink: итоговая карта
- 11. Типичные заблуждения
- 12. Практика
- 13. Проверьте себя
- 14. Чек-лист самопроверки
- 15. Что дальше
1. Цели урока
После изучения этого урока вы будете уметь выполнять следующие действия.
- Вы объясните, что такое протокол и почему соглашения о связи описывают слоями.
- Вы назовёте пять слоёв модели связи «для дронов» и вопрос, на который отвечает каждый слой.
- Вы отличите потоковую доставку байтов (serial-порт) от датаграммной (UDP) и объясните следствия для поиска границ сообщений.
- Вы объясните назначение каждой части кадра: синхробайт, длина, порядковый номер, адрес отправителя, идентификатор сообщения, полезная нагрузка и контрольная сумма.
- Вы объясните, что такое сериализация, и назовёте поля сообщения
HEARTBEATна уровне общего понимания. - Вы вычислите процент потерь, среднюю задержку и джиттер канала по формулам и интерпретируете результат вердиктом
OK/DEGRADED/UNUSABLE. - Вы примените слоёную модель к диагностике: определите, на каком уровне искать проблему «телеметрия не отображается».
2. Словарь урока
| Термин | Простыми словами |
|---|---|
| Протокол | Соглашение между двумя сторонами о формате и смысле передаваемых данных. |
| Слоёная модель | Разделение связи на уровни: каждый слой решает свою задачу и пользуется услугами нижнего. |
| Физический уровень | Способ передачи битов: электричество по проводам, радиоволны, свет. |
| Поток (stream) | Доставка байтов непрерывной чередой без встроенных границ сообщений — так работает serial-порт и TCP. |
| Датаграмма | Доставка самостоятельного блока байтов с сохранёнными границами — так работает UDP. |
| Кадр (frame) | Структурированный блок байтов: начало, длина, содержимое и проверка целостности. |
| Полезная нагрузка (payload) | Содержимое кадра без служебных полей — собственно данные сообщения. |
| Контрольная сумма (checksum, CRC) | Короткое число, вычисленное из содержимого: не совпало при пересчёте — кадр повреждён и отбрасывается. |
| Сообщение | Единица смысла: идентификатор плюс набор типизированных полей (например, HEARTBEAT). |
| Сериализация | Превращение структуры данных программы в байты для передачи и обратно. |
| Задержка (latency) | Время между отправкой и получением данных, миллисекунды. |
| Джиттер (jitter) | Нестабильность задержки: насколько сильно время доставки прыгает от пакета к пакету. |
| Потери (packet loss) | Доля отправленных пакетов, которые не дошли до получателя, в процентах. |
| Пропускная способность | Сколько данных канал способен передать за секунду: биты или байты в секунду. |
3. Зачем нужна слоёная модель: диагностика связи
Рассмотрим рабочую ситуацию. Вы запустили наземную станцию, подключились к аппарату — и телеметрия на экране не появляется. Начинающий разработчик в такой ситуации перебирает действия наугад: перезапускает программу, перетыкает кабель, меняет настройки. Инженер, который держит в голове слоёную модель, проверяет слои по порядку снизу вверх и на каждом слое задаёт свой вопрос.
- Физический уровень и транспорт: кабель вставлен, порт существует, программа открыла именно тот порт? Байты из порта вообще читаются — пусть даже бессмысленные?
- Кадры: удаётся ли в потоке байтов находить начала кадров, сходятся ли длины и контрольные суммы? Если кадры рассыпаются, проблема в скорости порта (baudrate) или в помехах.
- Сообщения: приходят ли сообщения нужных типов — например,
HEARTBEAT? Возможно, автопилот отправляет телеметрию на другой адрес или с другой частотой. - Приложение: не отфильтровала ли сама программа те сообщения, которые успешно дошли? Не ждёт ли она другого идентификатора системы?
Каждый слой имеет собственный инструмент проверки, и каждый инструмент вы напишете своими руками в этом модуле: чтение байтов из порта — в K17, UDP-обмен — в K18, разбор кадра и контрольная сумма — в K21, фильтрация сообщений — в модуле M5. Слоёная модель превращает диагностику из гадания в короткий перебор четырёх вопросов.
4. Пять слоёв связи «для дронов»
Протокол связи невозможно описать одним предложением, потому что он отвечает на слишком разные вопросы одновременно: какое напряжение на проводе, где начало блока данных, что означают числа внутри. Поэтому любое соглашение о связи удобно представлять как слоёную модель: несколько уровней, где каждый решает свой вопрос и пользуется услугами уровня ниже.
5. Приложение что делать со знанием: Mission Planner, ваш скрипт
4. Сообщения о чём именно речь: HEARTBEAT, ATTITUDE, COMMAND_LONG
3. Кадры где границы и цело ли: синхробайт, длина, CRC
2. Транспорт как доставляются блоки байтов: поток или датаграммы
1. Физический как передаются биты: провод, радиоволна, оптика
Бытовая аналогия — письмо. Физический уровень — дороги и транспорт: грузовик или самолёт, по которым едет почта. Транспорт — почтовая служба: она перевозит пачки корреспонденции и не читает содержимое. Кадр — конверт: на нём написан адрес, а печать позволяет получателю убедиться, что конверт не повреждён. Сообщение — текст письма, написанный на понятном обеим сторонам языке. Приложение — то решение, которое получатель примет после прочтения. Аналогия полезна, но помните, где она заканчивается: байты можно скопировать без износа, а «печать» кадра — контрольная сумма — проверяется автоматически и мгновенно.
Зафиксируем ту же модель в виде структуры данных — приёма, который пригодится, когда вы будете писать свой мониторинг связи:
Чистый код
# Пример: слоёная модель связи как словарь программы
layers = {
"physical": "провод UART, радиоканал или Ethernet",
"transport": "поток байтов serial или датаграммы UDP",
"frame": "кадр MAVLink: границы и контрольная сумма",
"message": "HEARTBEAT, ATTITUDE, COMMAND_LONG",
"application": "Mission Planner или скрипт на companion",
}
for name, description in layers.items():
print(f"{name}: {description}")
- Построчный разбор
Ниже приведён тот же фрагмент с пояснением после каждой смысловой строки. Строки, которые начинаются с последовательности символов «# →», содержат комментарий преподавателя. Эти строки не являются обязательной частью программы.
layers = { "physical": "провод UART, радиоканал или Ethernet", "transport": "поток байтов serial или датаграммы UDP", "frame": "кадр MAVLink: границы и контрольная сумма", "message": "HEARTBEAT, ATTITUDE, COMMAND_LONG", "application": "Mission Planner или скрипт на companion", } # → Словарь из урока K04: ключ — имя слоя (нижний слой первый), # → значение — короткое человеческое описание. Порядок вставки # → сохраняется, поэтому обход снизу вверх читается как модель. for name, description in layers.items(): print(f"{name}: {description}") # → Метод items() даёт пары ключ-значение; f-строка печатает # → каждую пару как строку мини-справочника.Python
Главное свойство слоёной модели: каждый слой можно заменить, не трогая остальные. Та же самая телеметрия MAVLink летит по проводу UART в бортовой сети и по UDP в симуляторе SITL — потому что MAVLink живёт на двух верхних из средних слоёв и не зависит от того, что находится под ним. Это утверждение станет итоговой картой раздела 10.
↑ К оглавлению5. Физический уровень и транспорт в мире БПЛА
Физический уровень отвечает на вопрос «как биты превращаются в физический процесс»: в электрические уровни напряжения на двух проводах UART, в радиоволны диапазона 433 или 915 МГц, в сигналы Ethernet и Wi-Fi. Его характеристики — разъёмы, уровни напряжений, частоты и скорость передачи в битах в секунду. Ошибка на этом уровне выглядит так: провод перепутан местами, скорость порта не совпала, радиоканал зашумлён.
Транспортный слой отвечает на вопрос «как блоки байтов доставляются от отправителя к получателю». Здесь важна не скорость, а форма доставки, и форм ровно две. Поток (stream) — непрерывная череда байтов без встроенных границ: так работают serial-порт и TCP. Получатель видит «реку» байтов и обязан сам искать в ней начала сообщений — эту задачу решает кадр из раздела 6. Датаграмма — самостоятельный блок с сохранёнными границами: так работает UDP. Каждый отправленный блок приходит отдельным пакетом (или не приходит вовсе), но если пришёл — его границы сохранены. Таблица ниже собирает каналы, которые встретятся в курсе.
| Канал | Где применяется в системе БПЛА | Форма доставки | Урок |
|---|---|---|---|
| UART (serial) | Бортовой компьютер-компаньон ↔ полётный контроллер; телеметрийные радиомодемы | поток байтов | K17 |
| USB | Наземный компьютер ↔ полётный контроллер: прошивка, калибровки, первый запуск | поток (виден системе как виртуальный COM-порт) | K17 |
| UDP | Симулятор SITL (порт 14550), телеметрия по Wi-Fi | датаграммы | K18 |
| TCP | Соединения наземных станций, сетевые мосты телеметрии | поток поверх надёжного соединения | K18 |
| I2C, SPI | Компаньон ↔ низкоскоростные датчики и периферия: OLED-экраны, АЦП, внешние компасы | обмен байтами по шине | K19 |
| CAN, DroneCAN | «Умная» периферия: GPS-приёмники, модули питания, сервоприводы | сообщения по общей шине | K20 |
6. Кадр: границы сообщения и целостность
Допустим, транспорт доставляет байты. В потоке serial-порта их «река» не имеет ни пауз, ни отметок: получатель видит числа одно за другим и не знает, где начинается и заканчивается осмысленный блок. Эту задачу решает кадр — заранее согласованная структура блока байтов. Любая кадровая структура, включая MAVLink, содержит примерно одни и те же части, каждая из которых отвечает на свой вопрос.
| Часть кадра | Вопрос, на который она отвечает |
|---|---|
| Синхробайт (стартовый маркер) | Где начинается кадр? Получатель ищет в потоке особое число, с которого кадр обязан начинаться. |
| Длина | Сколько байтов полезной нагрузки внутри? Зная длину, получатель понимает, где кадр заканчивается. |
| Порядковый номер (sequence) | Не было ли пропусков? Номера идут подряд; разрыв номеров означает потерю кадров в канале. |
| Адрес отправителя | Кто прислал кадр? В MAVLink это идентификаторы системы и компонента. |
| Идентификатор сообщения | О чём этот кадр? Число, по которому получатель выбирает схему разбора полей. |
| Полезная нагрузка (payload) | Собственно данные: поля сообщения в оговорённом порядке и размерах. |
| Контрольная сумма (checksum, CRC) | Не исказился ли кадр в пути? Отправитель вычисляет короткое число из содержимого, получатель пересчитывает его сам; не совпало — кадр отбрасывается молча. |
Контрольная сумма заслуживает отдельного пояснения, потому что именно она отделяет данные от шума. Радиоканал вносит искажения: отдельный бит может «перевернуться», и число в полезной нагрузке изменится. На глаз отличить искажённое значение от настоящего невозможно — оба выглядят правдоподобно. Контрольная сумма делает искажение видимым: пересчитанное значение не совпадёт с переданным, и приёмник отбросит кадр вместо того, чтобы поверить в ложные числа. Идею контрольных сумм вы соберёте руками на уроке K21, а точную анатомию кадра MAVLink 2 с шестнадцатеричным дампом настоящего HEARTBEAT разберёт урок K30.
7. Сообщение: идентификатор и поля
Полезная нагрузка кадра несёт сообщение — единицу смысла протокола. Сообщение состоит из идентификатора (какое это сообщение) и набора типизированных полей (какие именно числа внутри и что они означают). Превращение структуры данных программы в байты называется сериализацией, а обратный процесс — десериализацией; схема сообщения задаёт и порядок полей, и тип каждого поля, и единицы измерения.
Покажем на самом известном сообщении MAVLink — HEARTBEAT. Автопилот отправляет его примерно раз в секунду, и по одному факту приёма получатель понимает: система жива. Подробный разбор полей и работа с ними — уроки K29 и K32; сейчас достаточно общего знакомства.
| Поле | Что сообщает |
|---|---|
type |
Класс аппарата: мультиротор, самолёт, VTOL, наземная станция |
autopilot |
Семейство автопилота: ArduPilot, PX4 или «нет» для спутниковых компонентов |
base_mode |
Флаги состояния: взведены ли моторы, включён ли режим управления от бортового компьютера |
custom_mode |
Числовой идентификатор режима в нумерации автопилота: у ArduPilot это номера GUIDED, RTL, LOITER и других |
system_status |
Стадия работы: ожидание, активный полёт, критическое состояние, отказ |
mavlink_version |
Версия протокола MAVLink, которую использует отправитель |
Обратите внимание на деталь, которая станет важной в модуле M5: единицы измерения и масштаб полей задаёт схема сообщения, а не здравый смысл. В одних сообщениях углы приходят в радианах, в других — в сантиградусах; координаты — в градусах, умноженных на 10⁷, как вы видели на уроке K15. Дисциплина одна: читать схему поля до того, как интерпретировать число.
↑ К оглавлению8. Приложение: кто потребляет сообщения
Верхний слой — приложение: программа, ради которой вся передача и затевалась. В системе беспилотника приложений обычно несколько, и все они обмениваются одними и теми же сообщениями MAVLink.
- Наземная станция управления (Mission Planner, QGroundControl) рисует карту, приборы и телеметрию, отправляет команды оператора и файлы миссий.
- Бортовой компьютер-компаньон (наш будущий Raspberry Pi) выполняет «умные» задачи: сопровождение цели, облёт препятствий, передачу видео — и отправляет автопилоту команды верхнего уровня.
- Сам автопилот — тоже потребитель: он принимает команды, параметры и миссии и отвечает телеметрией и подтверждениями.
Адресация на этом слое устроена просто: каждый участник обмена имеет идентификатор системы (sysid) — например, аппарат обычно получает номер 1, а наземная станция — 255, — и идентификатор компонента (compid) внутри системы: автопилот, камера, модуль питания. Получатель смотрит на адрес в кадре и решает, ему сообщение или другому участнику. По одному каналу телеметрию могут слушать несколько приложений одновременно — как это правильно организовать, расскажет урок K37.
pymavlink (модуль M5), но уверенность в работе библиотеки появляется только тогда, когда вы понимаете, что происходит на каждом слое ниже приложения.9. Качество канала: задержка, джиттер, потери, пропускная способность
Канал связи характеризуется четырьмя числами, которые программист обязан уметь измерять и интерпретировать.
Первое — задержка (latency): время между отправкой данных и их получением, в миллисекундах. Различают одностороннюю задержку и время туда-обратно (RTT, round-trip time) — для команды с подтверждением важно второе. Задержка напрямую ограничивает управление: если команда «сместиться» доходит за полсекунды, аппарат будет «рыскать» вокруг цели, потому что контур управления реагирует на устаревшее состояние.
Второе — джиттер (jitter): нестабильность задержки. Канал со стабильными 100 миллисекундами часто лучше канала, у которого задержка прыгает от 20 до 200: предсказуемое запаздывание можно учесть, а случайные скачки ломают плавность управления. Учебная формула джиттера — средняя абсолютная разность соседних измерений задержки \(t_1, t_2, \ldots, t_n\):
\[ J = \frac{1}{n-1} \sum_{i=1}^{n-1} \left| t_{i+1} - t_i \right| \]
Третье — потери пакетов (packet loss): доля отправленных блоков, не дошедших до получателя. Если отправлено \(N_{sent}\) пакетов, а получено \(N_{recv}\), то процент потерь равен:
\[ L = \frac{N_{sent} - N_{recv}}{N_{sent}} \cdot 100\% \]
Небольшой процент потерь для радиоканала нормален, и протоколы это учитывают: телеметрия MAVLink передаётся периодическими потоками, и потерянный отсчёт просто заменится следующим через долю секунды. Опасны потери для команд: именно поэтому команды сопровождаются подтверждениями (COMMAND_ACK — разбор в модуле M5), а пропадание потока HEARTBEAT запускает защитную логику failsafe (урок K27).
Четвёртое — пропускная способность: сколько данных канал способен передать за секунду. Её измеряют в битах в секунду (бит/с) или в байтах в секунду (Б/с); между единицами перевод в восемь раз: 57600 бит/с — это примерно 7200 Б/с «грязными», а с учётом служебных полей кадров — примерно 4–6 КБ/с полезной телеметрии. Не путайте пропускную способность с задержкой: широкий канал не уменьшает время прохождения одного пакета.
| Канал (типичный случай) | Задержка | Пропускная способность | Потери |
|---|---|---|---|
| UART 115200: компаньон ↔ полётный контроллер | единицы миллисекунд и меньше | около 11 КБ/с | практически отсутствуют (провод) |
| Телеметрийный радиомодем 57600 бит/с | десятки миллисекунд | около 4–6 КБ/с | доли процента и выше, зависят от дистанции и помех |
| UDP по Wi-Fi или локально к SITL | единицы миллисекунд | десятки МБ/с | локально практически отсутствуют |
Числа в таблице — ориентиры для интуиции, а не спецификация: реальные характеристики всегда измеряются на конкретном оборудовании. Именно этим занимается практика урока.
Чистый код
# Пример: метрики качества канала (фрагмент k16_task1_link_quality.py)
def loss_percent(sent: int, received: int) -> float:
if sent <= 0:
raise ValueError(f"число отправленных должно быть больше нуля: {sent}")
if received < 0 or received > sent:
raise ValueError(f"received должно быть от 0 до sent, получено: {received}")
return (sent - received) / sent * 100.0
def jitter_ms(samples_ms) -> float:
if len(samples_ms) < 2:
raise ValueError("для джиттера нужно минимум два отсчёта")
total = 0.0
for i in range(1, len(samples_ms)):
total += abs(samples_ms[i] - samples_ms[i - 1])
return total / (len(samples_ms) - 1)
- Построчный разбор
Ниже приведён тот же фрагмент с пояснением после каждой смысловой строки. Строки, которые начинаются с последовательности символов «# →», содержат комментарий преподавателя. Эти строки не являются обязательной частью программы.
def loss_percent(sent: int, received: int) -> float: # → Чистая функция: отправили sent пакетов, дошло received — # → возвращает процент потерь по формуле из этого раздела. if sent <= 0: raise ValueError(f"число отправленных должно быть больше нуля: {sent}") # → Защита от деления на ноль: бессмысленный вход отклоняется # → исключением сразу (дисциплина уроков K08 и K12). if received < 0 or received > sent: raise ValueError(f"received должно быть от 0 до sent, получено: {received}") # → Получено не может быть больше, чем отправлено: # → такая комбинация означает ошибку учёта, а не чудо доставки. return (sent - received) / sent * 100.0 # → Доля потерянного, умноженная на сто: ответ в процентах. def jitter_ms(samples_ms) -> float: # → На вход — список измерений задержки в миллисекундах, # → упорядоченный по времени прихода пакетов. if len(samples_ms) < 2: raise ValueError("для джиттера нужно минимум два отсчёта") # → Разность соседних отсчётов существует только при двух и более измерениях. total = 0.0 for i in range(1, len(samples_ms)): total += abs(samples_ms[i] - samples_ms[i - 1]) # → Цикл с урока K05: накапливаем абсолютные разности соседних отсчётов. # → abs убирает знак: важен размер скачка, а не его направление. return total / (len(samples_ms) - 1) # → Среднее арифметическое разностей — формула J из этого раздела.Python
10. Где живёт MAVLink: итоговая карта
Соберём картину в один рисунок. MAVLink занимает два слоя — кадры и сообщения; всё, что ниже, он оставляет на усмотрение системы, всё, что выше, — на усмотрение программиста.
Приложение Mission Planner, скрипты на companion, логика автопилота
← ваш будущий Python
Сообщения HEARTBEAT, ATTITUDE, COMMAND_LONG, GLOBAL_POSITION_INT
← MAVLink
Кадры синхробайт, длина, seq, sysid/compid, msgid, payload, CRC
← MAVLink
Транспорт serial-поток / UDP-датаграммы / TCP / USB
← уроки K17–K18
Физический UART-провода, радиоканал модема, Ethernet, Wi-Fi
← уроки K17–K20
Отсюда следует ключевое свойство MAVLink, которое вы увидите в модулях M4 и M5: транспортная независимость. Симулятор SITL отдаёт телеметрию датаграммами UDP на порт 14550, реальный полётный контроллер — потоком байтов по UART или USB, а сообщения и кадры при этом одинаковы. Поэтому библиотека pymavlink подключается к любому каналу одной строкой соединения: в модуле M5 вы будете писать и udp:127.0.0.1:14550 для симулятора, и имя COM-порта для настоящего контроллера — и код выше строки подключения меняться не будет.
11. Типичные заблуждения
| Заблуждение | Почему оно опасно | Как правильно |
|---|---|---|
| «Протокол — это разъём или кабель» | Подмена уровней: один и тот же MAVLink работает и по проводу, и по радиоканалу, и по UDP | Протокол — соглашение о кадрах и сообщениях; физика и транспорт выбираются отдельно |
| «Байты из порта приходят готовыми сообщениями» | Serial — поток без границ: сообщение может прийти по частям или склеиться с соседним | Границы ищет кадровый уровень: синхробайт, длина, контрольная сумма |
| «Данные пришли — значит, они верные» | Искажённый бит меняет число незаметно: ложная телеметрия опаснее отсутствующей | Кадр без совпавшей контрольной суммы отбрасывается молча |
| «Потери пакетов — признак сломанной связи» | Небольшой процент потерь нормален для радиоканала; паника ведёт к неверным решениям | Следите за порогами: телеметрия переживёт потерю отсчёта, команды требуют подтверждений |
| «Маленькая задержка важнее стабильности» | Канал 20–200 мс вразброс ломает плавность управления сильнее, чем ровные 100 мс | Измеряйте и среднюю задержку, и джиттер; для контуров управления стабильность важнее |
| «Широкий канал означает маленькую задержку» | Пропускная способность и задержка — разные характеристики: толстая труба не ускоряет первый байт | Для команд важна задержка, для видео и логов — пропускная способность |
12. Практика
Урок теоретический, поэтому практика компактная: один модуль метрик качества канала и один файл тестов. Модуль превращает формулы раздела 9 в чистые функции — потерю, среднюю задержку, джиттер и сводный вердикт, — а тесты закрепляют их поведение, включая защиту от бессмысленных входных данных. Новых пакетов не требуется: только стандартная библиотека и установленный pytest. Порядок работы: создайте файлы задач 16.1 и 16.2 в каталоге code/M01_python, выполните прогоны (задача 16.3) и сохраните исходники в Git (задача 16.4).
Задача 16.1. Метрики качества канала
Файл практики: k16_task1_link_quality.py.
Создайте модуль метрик: чистые функции loss_percent() с защитой от бессмысленных входов, mean_ms() для средней задержки, jitter_ms() для нестабильности задержки и сводный вердикт link_verdict() с тремя уровнями: OK, DEGRADED, UNUSABLE. Демо разбирает три сценария состояния канала.
Ожидаемый результат. Демо печатает вердикт OK для канала без потерь с задержкой 4.8 мс, DEGRADED для радиомодема с потерями 4 % и UNUSABLE для канала с потерями 25 %.
- Полный текст файла k16_task1_link_quality.py
Скопируйте приведённое ниже содержимое в файл
code/M01_python/k16_task1_link_quality.pyв проекте PyCharm (виртуальное окружение) и запустите его из каталогаcode/M01_python. Ниже приведён полный учебный текст файла; он совпадает с файлом в репозитории курса.# k16_task1_link_quality.py # Цель: чистые функции для метрик качества канала связи — # потери, средняя задержка, джиттер и сводный вердикт. # Запуск из каталога code/M01_python: # python k16_task1_link_quality.py def loss_percent(sent: int, received: int) -> float: """Процент потерянных пакетов: сколько из отправленных не дошло.""" if sent <= 0: raise ValueError(f"число отправленных должно быть больше нуля: {sent}") if received < 0 or received > sent: raise ValueError(f"received должно быть от 0 до sent, получено: {received}") return (sent - received) / sent * 100.0 def mean_ms(samples_ms) -> float: """Среднее значение задержки по списку отсчётов, миллисекунды.""" if len(samples_ms) == 0: raise ValueError("пустой список отсчётов: среднее не определено") return sum(samples_ms) / len(samples_ms) def jitter_ms(samples_ms) -> float: """Джиттер: средняя абсолютная разность соседних отсчётов задержки, мс. Характеризует не среднюю величину задержки, а её нестабильность. """ if len(samples_ms) < 2: raise ValueError("для джиттера нужно минимум два отсчёта") total = 0.0 for i in range(1, len(samples_ms)): total += abs(samples_ms[i] - samples_ms[i - 1]) return total / (len(samples_ms) - 1) def link_verdict(loss_pct: float, mean_latency_ms: float, max_loss_pct: float = 5.0, max_latency_ms: float = 150.0) -> str: """Сводный вердикт качества канала: 'OK', 'DEGRADED' или 'UNUSABLE'. Учебные пороги по умолчанию: потери более max_loss_pct процентов или средняя задержка более max_latency_ms миллисекунд — канал непригоден; больше половины от любого порога — деградация. """ if loss_pct > max_loss_pct or mean_latency_ms > max_latency_ms: return "UNUSABLE" if loss_pct > max_loss_pct / 2.0 or mean_latency_ms > max_latency_ms / 2.0: return "DEGRADED" return "OK" if __name__ == "__main__": scenarios = ( ("хороший канал (SITL по Wi-Fi)", [4.0, 5.0, 4.0, 6.0, 5.0], 1000, 1000), ("радиомодем, умеренные помехи", [80.0, 120.0, 95.0, 140.0, 85.0, 110.0], 500, 480), ("канал с большими потерями", [90.0, 100.0], 200, 150), ) for title, samples, sent, received in scenarios: loss = loss_percent(sent, received) average = mean_ms(samples) jitter = jitter_ms(samples) verdict = link_verdict(loss, average) print( f"{title}: потери {loss:5.1f} %, средняя задержка {average:6.1f} мс, " f"джиттер {jitter:5.1f} мс -> {verdict}" )Python
Задача 16.2. Тесты метрик
Файл практики: k16_task2_test_link_quality.py.
Создайте файл тестов: четыре проверки потерь (включая отказ при нуле отправленных и при received больше sent), две проверки среднего, три проверки джиттера и четыре проверки вердикта — всего тринадцать тестов с допусками pytest.approx.
Ожидаемый результат. Команда python -m pytest k16_task2_test_link_quality.py -v показывает тринадцать строк PASSED и итог «13 passed».
- Полный текст файла k16_task2_test_link_quality.py
Скопируйте приведённое ниже содержимое в файл
code/M01_python/k16_task2_test_link_quality.pyв проекте PyCharm (виртуальное окружение) и запустите тесты командойpython -m pytest k16_task2_test_link_quality.py -vиз каталогаcode/M01_python. Ниже приведён полный учебный текст файла; он совпадает с файлом в репозитории курса.# k16_task2_test_link_quality.py # Тесты метрик качества канала K16: потери, задержка, джиттер, вердикт. # Запуск из каталога code/M01_python: # python -m pytest k16_task2_test_link_quality.py -v import pytest from k16_task1_link_quality import jitter_ms, link_verdict, loss_percent, mean_ms # --- потери ------------------------------------------------------------------ def test_loss_percent_basic(): assert loss_percent(100, 95) == pytest.approx(5.0) def test_loss_percent_zero_loss(): assert loss_percent(10, 10) == pytest.approx(0.0) def test_loss_percent_rejects_zero_sent(): with pytest.raises(ValueError): loss_percent(0, 0) def test_loss_percent_rejects_received_more_than_sent(): with pytest.raises(ValueError): loss_percent(10, 11) # --- средняя задержка --------------------------------------------------------- def test_mean_ms(): assert mean_ms([10.0, 20.0, 30.0]) == pytest.approx(20.0) def test_mean_ms_rejects_empty(): with pytest.raises(ValueError): mean_ms([]) # --- джиттер ------------------------------------------------------------------- def test_jitter_zero_for_stable_link(): assert jitter_ms([50.0, 50.0, 50.0]) == pytest.approx(0.0) def test_jitter_alternating(): assert jitter_ms([10.0, 20.0, 10.0, 20.0]) == pytest.approx(10.0) def test_jitter_rejects_single_sample(): with pytest.raises(ValueError): jitter_ms([42.0]) # --- сводный вердикт ------------------------------------------------------------ def test_verdict_ok(): assert link_verdict(0.0, 20.0) == "OK" def test_verdict_degraded(): assert link_verdict(4.0, 60.0) == "DEGRADED" def test_verdict_unusable_on_loss(): assert link_verdict(10.0, 20.0) == "UNUSABLE" def test_verdict_unusable_on_latency(): assert link_verdict(0.0, 400.0) == "UNUSABLE"Python
Задача 16.3. Прогоны демо и тестов (обязательно выполнить)
Выполните команды по порядку из каталога code/M01_python в терминале с активным виртуальным окружением.
# Каталог: code/M01_python (venv активен, новых пакетов не требуется)
python k16_task1_link_quality.py
python -m pytest k16_task2_test_link_quality.py -v
Ожидаемый результат. Демо печатает три сценария качества канала:
хороший канал (SITL по Wi-Fi): потери 0.0 %, средняя задержка 4.8 мс, джиттер 1.2 мс -> OK
радиомодем, умеренные помехи: потери 4.0 %, средняя задержка 105.0 мс, джиттер 38.0 мс -> DEGRADED
канал с большими потерями: потери 25.0 %, средняя задержка 95.0 мс, джиттер 10.0 мс -> UNUSABLE
tests: 13 passed
Обратите внимание на третий сценарий: средняя задержка в нём приемлема, но вердикт вынесен по потерям. Сводный вердикт обязан учитывать обе метрики — в этом и состоит идея многосторонней оценки канала.
Задача 16.4. Сохранение в Git
Добавьте в репозиторий оба файла урока и сделайте коммит — первый в модуле M3.
# Из корня репозитория курса
git add code/M01_python/k16_task1_link_quality.py code/M01_python/k16_task2_test_link_quality.py
git commit -m "K16: link quality metrics - loss, latency, jitter, verdict; 13 tests"
Ожидаемый результат. Команда git status показывает чистое рабочее дерево по файлам k16_*, в истории появился первый коммит модуля M3.
13. Проверьте себя
- Что такое протокол и почему соглашения о связи описывают слоями, а не одним текстом?
- Перечислите пять слоёв модели «для дронов» и вопрос, на который отвечает каждый слой.
- Чем потоковая доставка байтов отличается от датаграммной и какое следствие это имеет для поиска границ сообщений?
- Какую задачу решает каждая часть кадра: синхробайт, длина, порядковый номер, адрес, идентификатор сообщения, полезная нагрузка, контрольная сумма?
- Что такое сериализация и десериализация сообщений?
- Какие два слоя занимает MAVLink и почему он не зависит от того, что находится ниже?
- Чем задержка отличается от джиттера и почему стабильные 100 мс бывают лучше прыгающих 20–200 мс?
- Как вычисляется процент потерь и почему небольшой процент потерь нормален для радиоканала?
- Чем пропускная способность отличается от задержки? Приведите задачу, где критична каждая из них.
- Телеметрия не отображается в программе. Опишите порядок проверки по слоям снизу вверх.
14. Чек-лист самопроверки
- Я могу назвать пять слоёв модели связи и вопрос каждого слоя без подсказки.
- Я объясняю разницу потока и датаграммы на примере serial-порта и UDP.
- Я объясняю назначение каждой части кадра и роль контрольной суммы.
- Я могу пересказать, из чего состоит сообщение, на примере
HEARTBEAT. - Я знаю, где MAVLink живёт в слоёной модели и почему он транспортно-независим.
- Я создал оба файла практики в каталоге
code/M01_python. - Демо напечатало вердикты
OK,DEGRADEDиUNUSABLEдля трёх сценариев, и я могу объяснить каждый вердикт. - Команда
python -m pytest k16_task2_test_link_quality.py -vзавершается результатом 13 passed. - Я сделал git commit с файлами урока K16.
K16 чек-лист закрыт. Следующий урок — K17 «UART / Serial / USB-UART»: первый взгляд на физический уровень и транспорт, первое чтение настоящих байтов из порта.15. Что дальше
- Предыдущий урок: K15 · Геокоординаты: lat/lon/alt, haversine, bearing (модуль M2 завершён)
- Текущий урок: K16 · Слои связи «для дронов» — начало модуля M3 «Протоколы и интерфейсы»
- Следующий урок: K17 · UART / Serial / USB-UART: TX/RX/GND, baudrate, COM-порты Windows, библиотека
pyserial - Модуль M3 завершится лабораторной работой K22 (два процесса: передатчик фейковой телеметрии и приёмник-разборщик), после которой сдаётся гейт G2.