The tests/ directory held 50 laboratory programs and no tests. They model channels, run hundreds of repetitions and write CSV, PNG and reports; calling that a test suite blocked introducing a real one, because any pytest run would have collected the labs and re-executed every experiment. - move all 50 lab programs to experiments/ with git mv, preserving history - rewrite the 38 cross-imports between labs from tests.labNNN to experiments.labNNN - leave tests/ empty for actual fast checks of protocol/ - point quick_gate and the hook at the new layout and add experiments/ to the syntax sweep - update the paths quoted in the Lab042 specification and the verifier agent definition This also defuses the import-time work finding without touching 41 files: the labs still create directories and write files on import, but nothing imports them now except the gate, which does so deliberately. Gate passes: syntax clean, protocol imports, 15 lab modules import, 2 functional suites run. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
287 lines
27 KiB
Markdown
287 lines
27 KiB
Markdown
# Lab042 — техническое задание
|
||
|
||
## Передача JPEG через настоящий радиотракт: кабельная петля на одном PlutoSDR
|
||
|
||
Документ предназначен для исполнителя, не участвовавшего в предыдущих лабораторных. Всё, что нужно для работы, изложено здесь.
|
||
|
||
---
|
||
|
||
## 1. Зачем эта лабораторная
|
||
|
||
За Lab001–Lab041 собран весь тракт передачи изображения, кроме одного звена: **передатчик ни разу не включался**. Во всём проекте нет ни одного вызова передачи через SDR.
|
||
|
||
Цель Lab042 — соединить уже готовые части и получить **изображение, прошедшее через настоящий радиотракт**. Не улучшить, не оптимизировать, не измерить дальность. Получить картинку на выходе.
|
||
|
||
Всё, что не требуется для этой цели, в Lab042 не делается.
|
||
|
||
---
|
||
|
||
## 2. Что уже готово и должно быть переиспользовано
|
||
|
||
Ничего из перечисленного писать заново не нужно.
|
||
|
||
| Звено | Где взять |
|
||
|---|---|
|
||
| Разбиение JPEG на фрагменты и сборка обратно | `protocol/image_fragments.py`, примеры в `experiments/lab007_image_fragmentation.py` |
|
||
| Пакет с CRC32, кодирование и разбор | `protocol/packet.py` |
|
||
| Подбор размера и качества JPEG | `experiments/lab010_image_optimization.py`, готовые профили в `data/processed/lab010` |
|
||
| Формирование радиокадра с преамбулой и маркером | `build_radio_frame` в `experiments/lab018_bpsk_radio_frame.py` |
|
||
| Фильтр приподнятого косинуса | `root_raised_cosine_taps` там же |
|
||
| Модуляция BPSK | `bpsk_modulate` там же |
|
||
| Поиск кадра в потоке отсчётов | `find_radio_frame` в `experiments/lab019_bpsk_receiver.py` |
|
||
| Оценка и коррекция ухода частоты | `estimate_carrier_parameters`, `correct_phase_and_frequency` там же |
|
||
| Демодуляция и разбор кадра | `bpsk_demodulate`, `decode_radio_frame` там же |
|
||
| Работа с Pluto на приём | `experiments/lab024a_pluto_rx_capture.py` |
|
||
|
||
**Единственное, чего нет в проекте — передача.** Это и есть основной объём работы.
|
||
|
||
---
|
||
|
||
## 3. Почему петля на одном устройстве, а не два устройства
|
||
|
||
Передатчик и приёмник одного PlutoSDR работают от общего опорного генератора. Поэтому между ними практически нет ухода несущей частоты и не расходятся часы.
|
||
|
||
Это снимает ровно те две проблемы, которые в проекте проработаны слабее всего, и позволяет получить картинку сразу. Разведение на два устройства — отдельная лабораторная Lab043, там появится настоящий уход частоты, и вот там понадобятся наработки Lab021–Lab023.
|
||
|
||
**Не пытайтесь сделать Lab042 и Lab043 одной работой.** Разница между ними — вся сложность синхронизации.
|
||
|
||
---
|
||
|
||
## 4. Наличное оборудование
|
||
|
||
Подтверждено по состоянию на 10 августа 2026 года.
|
||
|
||
| Позиция | Состояние |
|
||
|---|---|
|
||
| **Pluto+** — SDR, 2 приёмника и 2 передатчика, гигабитный Ethernet | есть |
|
||
| RTL-SDR — приёмный донгл | есть, понадобится в Lab043 |
|
||
| Кабели SMA | будут найдены |
|
||
| Антенны: штыревые на магнитном основании, короткая штыревая | есть, **в Lab042 не используются** |
|
||
| Рамочная антенна Donut AM (0,5–2 МГц) | к проекту отношения не имеет |
|
||
| **Аттенюатор AT30S**, 30 дБ, до 200 Вт, разъёмы SMA | есть |
|
||
|
||
Аппаратных препятствий к началу работы нет. Запас по мощности у аттенюатора избыточен на много порядков — рассчитан на передатчики в сотни ватт, а у нас доли милливатта, — но это никак не мешает: ослабление от уровня сигнала не зависит.
|
||
|
||
Важно: устройство — **Pluto+**, а не оригинальный ADALM-PLUTO. Отличия, которые имеют значение:
|
||
|
||
- связь по Ethernet, а не по USB; адрес `ip:192.168.2.1` уже подтверждён работой Lab024a;
|
||
- два приёмных и два передающих канала; в Lab042 используется только первый канал каждого;
|
||
- вопреки надписи на корпусе, устройство сообщает о себе как `Analog Devices PlutoSDR Rev.C (Z7010-AD9361)` и представлено **одним приёмным и одним передающим каналом**: буферные устройства `cf-ad9361-lpc` и `cf-ad9361-dds-core-lpc` несут по паре каналов I и Q. Поэтому класс `adi.Pluto` подходит, переход на `adi.ad9361` не требуется;
|
||
- чип **AD9361**, а не урезанный AD9363. Диапазон перестройки приёмника от 70 МГц до 6 ГГц, передатчика от 46,9 МГц до 6 ГГц. Целевой диапазон проекта ниже 350 МГц устройству доступен штатно, без доработки прошивки. Для Lab044 это снимает ранее открытый вопрос;
|
||
- прошивка `v0.32-1-g7bdc-dirty`, ядро 4.19. Обновление в рамках Lab042 не выполняется.
|
||
|
||
## 5. Требования к схеме и сохранности оборудования
|
||
|
||
Разрешения на работу в эфире у владельца проекта есть, вопрос законности здесь не рассматривается. Перечисленное ниже защищает оборудование и чистоту опыта.
|
||
|
||
1. **Антенны убрать со стола до начала работы.** Причина техническая: антенна на передатчике или приёмнике создаёт паразитный путь распространения параллельно кабельному. Уровень на входе приёмника перестаёт соответствовать расчёту, появляется переотражённый сигнал, и при отказе будет невозможно понять, что именно не сработало. Кабельная петля ценна тем, что в ней известно всё; антенна это свойство разрушает.
|
||
2. Передатчик не включать при отключённом кабеле: выход без нагрузки вреден для оконечного каскада.
|
||
3. Аттенюатор AT30S включается **в разрыв** между выходом передатчика и входом приёмника. Не параллельно, не на свободный порт — именно последовательно в тракт.
|
||
4. Перед первым включением передатчика убедиться глазами, что аттенюатор действительно в цепи. Это единственное, что стоит между опечаткой в настройке усиления и сгоревшим приёмником.
|
||
|
||
### Расчёт уровней
|
||
|
||
| Величина | Значение |
|
||
|---|---|
|
||
| Выход передатчика Pluto+ при усилении 0 дБ | около **+7 дБм** |
|
||
| Предельно допустимый уровень на входе приёмника | около **+2,5 дБм** |
|
||
| Ослабление аттенюатора | 30 дБ |
|
||
| Уровень на входе приёмника при усилении 0 дБ | около **−23 дБм** |
|
||
|
||
Вывод: **с аттенюатором в разрыве весь диапазон `tx_hardwaregain_chan0` от −89 до 0 дБ безопасен для приёмника.** Запас до порога повреждения составляет более 25 дБ даже в самом худшем случае.
|
||
|
||
**Порядок подбора усиления:**
|
||
|
||
1. Начать с `tx_hardwaregain_chan0 = -30`. Это даёт около −53 дБм на входе приёмника — заведомо чистый и заведомо безопасный уровень.
|
||
2. Если сигнал слишком слаб, поднимать шагами по 10 дБ.
|
||
3. Если созвездие разваливается при сильном сигнале — это насыщение приёмника, лечится **уменьшением** усиления, а не увеличением.
|
||
|
||
Без аттенюатора работать запрещено даже при малом усилении: защита нужна не от расчётного уровня, а от ошибки оператора.
|
||
|
||
---
|
||
|
||
## 6. Программное окружение: препятствие, которое надо снять первым
|
||
|
||
Проверка окружения 10 августа 2026 года показала следующее.
|
||
|
||
Интерпретатор, доступный в командной строке — `C:\Users\user\AppData\Local\Programs\Python\Python313\python.exe`, версия 3.13.1. В нём **отсутствуют `pyadi-iio` и `libiio`**, то есть работать с Pluto+ из него невозможно. Также отсутствует `scipy`, который нужен лабораторным Lab020–Lab022, Lab024b и Lab025.
|
||
|
||
При этом Lab024a когда-то отработала с настоящим устройством. Значит, существует второе окружение — по всей видимости, среда Anaconda, — где всё установлено, и запуск шёл оттуда.
|
||
|
||
**Первое действие исполнителя: определить, в каком окружении запускаются лабораторные, и зафиксировать это.** Пока это не сделано, невозможно ни повторить Lab024a, ни начать Lab042.
|
||
|
||
Требуется:
|
||
|
||
1. Найти окружение, где установлены `pyadi-iio` и `libiio`, и проверить связь с устройством.
|
||
2. Записать в отчёт: путь к интерпретатору, версию Python, версии `pyadi-iio` и `libiio`.
|
||
3. Если такого окружения нет — установить `pyadi-iio` и `libiio`, зафиксировав версии.
|
||
|
||
Расчётные лабораторные, включая Lab041, в окружении без `pyadi-iio` запускаются нормально — им нужны только `numpy`, `matplotlib` и `opencv`. Разделение аппаратных и расчётных зависимостей относится к этапу стабилизации и в Lab042 не решается, но результат проверки надо записать: он понадобится при составлении описания окружения.
|
||
|
||
---
|
||
|
||
## 7. Параметры тракта
|
||
|
||
Значения тракта взяты из Lab018 и менять их в этой лабораторной **запрещено**, за единственным исключением, описанным сразу под таблицей. Причина запрета: переход на железо сам по себе вносит достаточно неизвестных. Менять одновременно и параметры сигнала — значит потерять возможность понять причину отказа.
|
||
|
||
| Параметр | Значение | Источник |
|
||
|---|---|---|
|
||
| Символьная скорость | 20 000 симв/с | `SYMBOL_RATE`, Lab018 |
|
||
| Отсчётов на символ | **128** | изменено против Lab018, см. ниже |
|
||
| Частота дискретизации | **2 560 000 Гц** | произведение двух предыдущих |
|
||
| Скругление фильтра | 0,35 | `RRC_ROLLOFF`, Lab018 |
|
||
| Длина фильтра | 10 символов | `RRC_SPAN_SYMBOLS`, Lab018 |
|
||
| Преамбула | 64 бита | `PREAMBLE_BIT_COUNT`, Lab018 |
|
||
| Слово синхронизации | 0xD391 | `RADIO_SYNC_WORD`, Lab018 |
|
||
| Защитный интервал | 16 символов | `GUARD_SYMBOL_COUNT`, Lab018 |
|
||
| Амплитуда передачи | 0,70 | `TX_AMPLITUDE`, Lab018 |
|
||
|
||
### Обязательное отступление от Lab018: частота дискретизации
|
||
|
||
Опрос устройства 10 августа 2026 года показал, что **минимальная частота дискретизации AD9361 составляет 2 083 333 Гц**. Частота 640 000 Гц из Lab018 физически недостижима: она втрое ниже предела. Понизить её можно только включением встроенного КИХ-фильтра с прореживанием, что добавляет к первому включению передатчика ещё один непроверенный механизм.
|
||
|
||
Принято решение поднять число отсчётов на символ с 32 до 128. Тогда:
|
||
|
||
- символьная скорость остаётся **20 000 симв/с**, как в Lab018;
|
||
- скругление, длина фильтра, преамбула, слово синхронизации и защитный интервал — без изменений;
|
||
- частота дискретизации становится 20 000 × 128 = **2 560 000 Гц**, что выше минимума с запасом.
|
||
|
||
Меняется **только степень передискретизации** — величина, относящаяся к стыку с устройством, а не к самому сигналу. Форма спектра, скорость и структура кадра остаются теми же, что проверены в Lab018–Lab023. Это единственное разрешённое расхождение с Lab018, и оно вынужденное.
|
||
|
||
Полезная скорость получается около 20 кбит/с. Для картинки в несколько килобайт это единицы секунд. Этого достаточно: цель — картинка, а не скорость.
|
||
|
||
Побочное следствие: фильтр приподнятого косинуса при 128 отсчётах на символ и длине 10 символов получается на 1281 отвод. Свёртку следует выполнять через быстрое преобразование Фурье (`scipy.signal.fftconvolve`), иначе формирование сигнала займёт недопустимо много времени.
|
||
|
||
### Параметры PlutoSDR
|
||
|
||
| Параметр | Значение | Примечание |
|
||
|---|---|---|
|
||
| Адрес устройства | `ip:192.168.2.1` | как в Lab024a |
|
||
| Несущая передачи и приёма | 435 МГц | обе одинаковые |
|
||
| Частота дискретизации | 2 560 000 | совпадает с трактом, минимум устройства 2 083 333 |
|
||
| Полоса радиочастотного тракта | 200 000 Гц | минимум Pluto, сигнал занимает около 27 кГц |
|
||
| Усиление передатчика | начать с −30 дБ | с аттенюатором безопасен весь диапазон, см. раздел 5 |
|
||
| Режим усиления приёмника | `manual` | автоматика мешает измерениям |
|
||
| Усиление приёмника | начать с 20 дБ | подобрать по амплитуде |
|
||
| Циклический буфер передачи | **выключен** | см. раздел 9, ловушка 1 |
|
||
|
||
Про несущую: 435 МГц выбрана потому, что нижняя граница PlutoSDR составляет около 325 МГц, а целевой диапазон проекта ниже 350 МГц находится у самого края. Поскольку передача идёт по кабелю и в эфир не выходит, выбор частоты здесь ни на что не влияет. Вопрос рабочего диапазона решается отдельно перед Lab044.
|
||
|
||
---
|
||
|
||
## 8. Порядок работы
|
||
|
||
**Передающая сторона**
|
||
|
||
1. Взять исходное изображение из `data/raw`, привести к профилю из Lab010 так, чтобы JPEG получился не крупнее 5 КБ.
|
||
2. Разбить на фрагменты через `protocol/image_fragments.py`.
|
||
3. Каждый фрагмент упаковать в пакет с CRC32 через `protocol/packet.py`.
|
||
4. Каждый пакет обернуть в радиокадр через `build_radio_frame`.
|
||
5. Промодулировать BPSK и отфильтровать.
|
||
6. Привести к целым числам и передать.
|
||
|
||
**Приёмная сторона**
|
||
|
||
1. Запустить приём **до** начала передачи.
|
||
2. Найти кадры через `find_radio_frame`.
|
||
3. Скорректировать фазу и частоту.
|
||
4. Демодулировать, разобрать кадр, проверить CRC32.
|
||
5. Собрать изображение из уцелевших фрагментов и сохранить на диск.
|
||
|
||
Приём и передача идут в одном процессе. Приём запускается первым — иначе начало первого кадра теряется безвозвратно.
|
||
|
||
---
|
||
|
||
## 9. Ловушки, на которых спотыкаются в первый раз
|
||
|
||
Перечислено то, что почти наверняка будет сделано неправильно без предупреждения.
|
||
|
||
1. **Циклический буфер передачи.** У PlutoSDR буфер передачи по умолчанию повторяется бесконечно. Кажется, что передан один кадр, а передаётся бесконечный поток. Признак: приёмник видит тысячи копий одного кадра, а передача не заканчивается. Буфер надо явно выключить и явно освобождать после передачи.
|
||
|
||
2. **Масштаб отсчётов.** PlutoSDR принимает целые числа, а не дробные значения от минус единицы до единицы. Готовый сигнал надо умножить на масштабный множитель, обычно 2 в степени 14. Признак ошибки: передаётся почти тишина, приёмник ничего не находит.
|
||
|
||
3. **Приём запущен после передачи.** Начало кадра теряется, преамбула не находится. Признак: `find_radio_frame` не срабатывает при заведомо исправном сигнале.
|
||
|
||
4. **Размер приёмного буфера меньше кадра.** Кадр не помещается целиком, поиск преамбулы срывается. Буфер должен вмещать кадр с запасом минимум вдвое.
|
||
|
||
5. **Автоматическая регулировка усиления приёмника.** Меняет амплитуду в процессе и портит оценку. Режим должен быть ручным.
|
||
|
||
6. **Насыщение приёмника.** При слишком слабом ослаблении сигнал упирается в потолок и созвездие разваливается, хотя уровень кажется отличным. Признак: демодуляция не работает при очень сильном сигнале. Лечится увеличением ослабления, а не усилением.
|
||
|
||
7. **Переходный процесс в начале передачи.** Первые несколько десятков отсчётов недостоверны. Поэтому в тракте есть защитный интервал в 16 символов — его нельзя убирать.
|
||
|
||
---
|
||
|
||
## 10. Что считается выполненной работой
|
||
|
||
Лабораторная принимается, если выполнено всё перечисленное.
|
||
|
||
**Обязательный результат**
|
||
|
||
1. Файл принятого изображения на диске, открывающийся просмотрщиком.
|
||
2. Принятое изображение совпадает с переданным при отсутствии потерь — сравнение по контрольной сумме всего файла.
|
||
3. Работа воспроизводится: два запуска подряд дают одинаковый успешный результат.
|
||
|
||
**Обязательные измерения**
|
||
|
||
| Величина | Как получить |
|
||
|---|---|
|
||
| Размер переданного JPEG, байт | из файла |
|
||
| Число фрагментов и пакетов | из разбиения |
|
||
| Число принятых кадров | счётчик приёмника |
|
||
| Число кадров, не прошедших CRC32 | счётчик приёмника |
|
||
| Доля успешно принятых фрагментов | отношение |
|
||
| Полное время передачи, секунды | измерение |
|
||
| Фактическая полезная скорость, кбит/с | расчёт из двух предыдущих |
|
||
| Усиление передатчика и приёмника | из настроек устройства |
|
||
| Ослабление аттенюатора, дБ | 30, AT30S |
|
||
|
||
**Обязательные файлы в `data/processed/lab042`**
|
||
|
||
- принятое изображение;
|
||
- CSV с измерениями из таблицы выше;
|
||
- текстовый отчёт;
|
||
- график созвездия принятого сигнала;
|
||
- график спектра принятого сигнала.
|
||
|
||
**Требования к коду**
|
||
|
||
- Ничего из `protocol/` не переписывать. Только использовать.
|
||
- Параметры тракта из раздела 5 не менять.
|
||
- Все константы вынести в начало файла с указанием источника, как это сделано в Lab041.
|
||
- Никаких вписанных руками результатов: каждое число в отчёте должно вычисляться из измерений текущего запуска. Это отдельно проверяется при приёмке.
|
||
- Файл лабораторной: `experiments/lab042_pluto_image_loopback.py`, запуск через `python -m experiments.lab042_pluto_image_loopback` из корня проекта.
|
||
|
||
---
|
||
|
||
## 11. Что в этой лабораторной делать не надо
|
||
|
||
Перечислено, чтобы не расширять объём.
|
||
|
||
- Не реализовывать повторную передачу потерянных фрагментов. Потери фиксируются, но не восстанавливаются.
|
||
- Не подключать исправление стираний из `protocol/packet_erasure_fec.py`.
|
||
- Не поднимать скорость и не менять модуляцию.
|
||
- Не выходить в эфир и не подключать антенну. Это вопрос не разрешений, а объёма: эфирная передача — предмет Lab044, и смешивать её с первым включением передатчика нельзя.
|
||
- Не работать с двумя устройствами.
|
||
- Не трогать логику сеансов, аварийной остановки и приоритетов — она к передаче картинки отношения не имеет.
|
||
|
||
Всё это относится к последующим лабораторным.
|
||
|
||
---
|
||
|
||
## 12. Порядок приёмки
|
||
|
||
1. Исполнитель выполняет работу и сообщает о готовности.
|
||
2. Проверяется `python tools/quick_gate.py` — должен проходить.
|
||
3. Проводится аудит: соответствие разделу 10, отсутствие вписанных результатов, происхождение каждого числа в отчёте.
|
||
4. При наличии замечаний они оформляются списком с указанием файла и строки.
|
||
5. После приёмки в `PROJECT_LOG.md` добавляется новая запись по принятому в проекте образцу.
|
||
|
||
---
|
||
|
||
## 13. Что дальше
|
||
|
||
- **Lab043** — разведение на два устройства: передаёт PlutoSDR, принимает RTL-SDR. Появляется настоящий уход несущей частоты и расхождение часов. Здесь понадобятся Lab021–Lab023.
|
||
- **Lab044** — выбор рабочего диапазона и первый выход в эфир на малой мощности. Разрешения имеются, поэтому вопрос сводится к техническому выбору диапазона, мощности и антенны.
|
||
- Повышение скорости, исправление стираний и передача последовательности кадров — после того, как картинка устойчиво проходит.
|