# USAGE.md — руководство пользователя pluto-link Практический быстрый старт к [README.md](../README.md). README — инженерный журнал (бюджет CPU, история отладки, §12-этапы, техдолг); этот файл — короткий путь «склонировал → собрал → залил → поднял приём/передачу». Детали и обоснования цифр — в README §12 и [BENCHMARK.md](BENCHMARK.md). ## 1. Что это Радиолиния точка-точка на двух ADALM-Pluto+ (клон, Zynq-7020, 2 × Cortex-A9). Весь сигнальный тракт (OFDM + RS(255,223) FEC) исполняется на ARM **внутри** Pluto; хост (Raspberry Pi, aarch64) нужен только для кросс-сборки, заливки бинарей и снятия логов. Рабочая точка: симплекс 915 МГц, ~120 КБ/с (legacy) или ~95 КБ/с с групповым FEC 16+2 (`-G`), передача файлов байт-в-байт. ## 2. Быстрый старт (5 команд) На свежем клоне репозитория (на хосте — Raspberry Pi): ```bash ./scripts/build_deps.sh # 1. один раз: кросс-либы в ~/xarm make # 2. собрать tx/rx/bench → build/ make deploy PLUTO_A=192.168.2.1 PLUTO_B=192.168.3.1 # 3. scp -O на обе платы в /tmp ssh root@192.168.3.1 '/tmp/receiver -f 915000000 -r 1920000 -b 1500000 -c rs8 -u local: > /tmp/out.bin' # 4. приём (B) ssh root@192.168.2.1 'cat /tmp/file.bin | /tmp/transmitter -f 915000000 -r 1920000 -b 1500000 -g -30 -p 4000 -c rs8 -G -u local:' # 5. передача (A) ``` Или всё сразу: **`scripts/test_file_link.sh`** — генерит тестовый файл, гоняет A → эфир → B и сверяет md5. - Пароль root на Pluto — `analog`. `apt install sshpass` убирает ручной ввод пароля (скрипты подхватывают его автоматически). - rootfs платы в RAM: после перезагрузки Pluto бинари из `/tmp` пропадают — залить заново (`make deploy`). - ⚠ Перед эфиром отлаживай по кабелю с аттенюатором 30–40 дБ и TX gain −40 (`-g -40`), только потом антенны и дистанция (правило №7 CLAUDE.md). ## 3. Типовые команды | Сценарий | Команда | |---|---| | Собрать всё | `make` | | Залить на обе платы | `make deploy` (или `scripts/deploy.sh `) | | E2E-тест с md5-сверкой | `scripts/test_file_link.sh` | | Короткий файл ≤1 МБ | TX `-p 3000` (без `-G`) | | Длинная / соук-передача | TX `-p 4000 -G` (групповой FEC 16+2, README §12.7) | | Первый эфир по кабелю | `TXGAIN=-40 scripts/test_file_link.sh` | | OTA на антенны | `TXGAIN=-20 scripts/test_file_link.sh` | | Соук 10 МБ | `GROUPFEC=1 PAUSE=4000 scripts/test_file_link.sh 10240` | | Бенчмарк PHY на плате | `make bench && scripts/deploy.sh bench && ssh root@ '/tmp/ofdm_bench 1.92'` | | Без FEC (отладка PHY) | `-c none` на обеих сторонах | ## 4. Параметры Флаги передатчика: `-f -r -b -g -a -p -u -c -G`. Приёмник понимает только общие: `-f -r -b -u -c`. Колонка «Сторона» отмечает передатчик-специфичные (`-g/-a/-p/-G`). | Флаг | Значение | По умолч. | Сторона | Примечание | |---|---|---|---|---| | `-f` | несущая, Гц | 915000000 | TX + RX | одинаково на обеих | | `-r` | sample rate, Гц | 1920000 | TX + RX | **не поднимать** — бюджет CPU (правило №5, BENCHMARK.md) | | `-b` | полоса, Гц | 1500000 | TX + RX | — | | `-g` | TX gain, дБ | −30 | TX | кабель −40, антенны от −20 | | `-a` | амплитуда сигнала | 0.20 | TX | обычно не трогать | | `-p` | пауза между кадрами, мкс | 2000 | TX | ≤1 МБ → 3000; длинные / `-G` → **4000** (§12.7) | | `-c` | FEC: `rs8` \| `none` | rs8 | TX + RX | должно совпадать на обеих сторонах | | `-G` | групповой FEC 16+2 | выкл | TX | RX автодетект по флагам в hdr[11] | | `-u` | URI libiio | — | TX + RX | в тракте всегда `local:` (правило №2) | | `-h` | справка | — | TX + RX | — | ## 5. Troubleshooting | Симптом | Вероятная причина | Что делать | |---|---|---| | `out.bin` = 0 байт | RX ничего не поймал: частота / усиление / кабель | сверь `-f`, TX gain, аттенюатор или антенны | | размер меньше исходного | потерянные кадры (симплекс, ARQ нет) | включи `-G`; смотри разрывы seq в `rx.log` | | размер равен, md5 не сходится | header_len ≠ 12 (техдолг #1) или fec_decode (#3) | 12-байт заголовок на обеих сторонах; проверь `-c` | | много `Overrun` в `rx.log` | ядро 0 срывается под нагрузкой | подними `-p` (3000 → 4000), см. §12.7 | | 10 МБ-соук падает на `-p 3000` | overrun-пачки рвут стирающий FEC | только `-p 4000` — проверенная точка | | `Invalid argument (22)` на rate | AD9361 без FIR не берёт 1.92 Msps | нужен FIR через `ad9361_set_bb_rate` (уже в коде) | | `Reed-Solomon codes unavailable` | liquid собран без libfec | пересобрать liquid (`rm libliquid.a`, затем `make`) | | `scp` виснет / `subsystem request failed` | в прошивке v0.38 нет sftp-server | только `scp -O` (deploy.sh уже так делает) | | после ребута Pluto бинарей нет | rootfs в RAM, `/tmp` очищается | `make deploy` заново | | ssh каждый раз просит пароль | не установлен sshpass | `apt install sshpass` (пароль `analog`) | Более глубокая диагностика линка — README §11; журнал этапов и рабочие точки — README §12; бюджет CPU и модель паузы — [BENCHMARK.md](BENCHMARK.md). ## 6. UDP-туннель (`udp_gw`) Прозрачный UDP-шлюз поверх линка (README §10 п.8, §12.10): вместо файла через `cat`/`>` в тракт можно завести живой UDP-трафик. Ядро (transmitter/receiver) не меняется — `udp_gw` инкапсулирует датаграммы в самосинхронизирующиеся рекорды (8 Б оверхеда) поверх того же байтового потока. ### Топология: кто где слушает RPi-хост в этой схеме НЕ часть тракта данных — он только запускает команды по ssh и (в тесте) заливает/забирает файл по scp. Порты 6000/6001 — `127.0.0.1` **на каждой плате Pluto своя, отдельная**, а не адрес хоста: пакет от `udp_gw -l 6000` на плате A никогда не покидает саму плату A, пока не выйдет в эфир через `transmitter`; так же на B. ``` ================== Pluto A (TX) — 192.168.2.1 =================== внешний источник UDP на плате A (в scripts/test_udp_tunnel.sh — это /tmp/probe_in.bin через udp_probe send, запущенный по ssh НА ЭТОЙ ЖЕ плате) | | UDP -> 127.0.0.1:6000 v (localhost ПЛАТЫ A, не RPi-хоста!) udp_gw -l 6000 | stdout — рекорды (magic D5 5D + len/id/hcrc8) v transmitter -f 915e6 -r 1920000 -b 1500000 -g -20 -p 3000 -c rs8 -G -u local: | v =================================================================== ЭФИР 915 МГц, симплекс =================================================================== | v receiver -f 915e6 -r 1920000 -b 1500000 -c rs8 -u local: | stdout — рекорды v udp_gw -d 127.0.0.1:6001 | UDP -> 127.0.0.1:6001 v (localhost ПЛАТЫ B, не RPi-хоста!) внешний приёмник UDP на плате B (в scripts/test_udp_tunnel.sh — это udp_probe recv, запущенный по ssh НА ЭТОЙ ЖЕ плате, пишет в /tmp/probe_out.bin) ================== Pluto B (RX) — 192.168.3.1 =================== ``` Если источник/приёмник UDP — не тестовый `udp_probe`, а реальный внешний прибор, порт `-l`/`-d` слушает не только `127.0.0.1`, а нужный интерфейс платы (или `0.0.0.0`) — тогда трафик действительно приходит извне платы A и уходит наружу с платы B, а не варится в loopback, как в тесте. Пример (симплекс, антенны, те же флаги линка, что и в §3): ```bash # B — приёмник: расшифровать поток в UDP на 127.0.0.1:6001 ssh root@192.168.3.1 \ '/tmp/receiver -f 915000000 -r 1920000 -b 1500000 -c rs8 -u local: \ | /tmp/udp_gw -d 127.0.0.1:6001' # A — передатчик: слушать UDP на :6000, завернуть в линк ssh root@192.168.2.1 \ '/tmp/udp_gw -l 6000 \ | /tmp/transmitter -f 915000000 -r 1920000 -b 1500000 -g -20 -p 3000 -c rs8 -G -u local:' ``` Автотест по этой схеме (генерирует файл, гоняет через оба шлюза, сверяет md5): **`TXGAIN=-20 scripts/test_udp_tunnel.sh`** — на антеннах правило №7 (кабель+аттенюатор) не применяется: шлюз не трогает PHY, его логика проверена `make gwtest` + loopback (README §12.10). **Потолок и семантика.** Вход быстрее линка (~107 КБ/с на `-G -p 3000`) — `udp_gw -l` дропает датаграммы с ростом счётчика в stderr (не блокирует приём — так и задумано для UDP). У шлюза нет EOF-протокола: завершение — `kill`/`SIGTERM`, не END-хендшейк (тот остаётся фичей файловых передач). | Что нужно | Профиль | Свойства | |---|---|---| | Надёжность, задержка не критична | FDD (`-F`) + `-G` | 0 потерь (ARQ добирает то, что не закрыл паритет), HOL-задержка на ретрансмите | | Ровная задержка, редкие потери ок | симплекс + `-G` | GF 16+2 закрывает ≤2 стёртых кадра/группу без обратного канала; сверх этого — дыра в id, поток не рвётся | Диагностика по счётчикам `udp_gw` (печатаются в stderr раз в секунду): | Счётчик | Где | Значит | |---|---|---| | `дропов` | `-l` (вход) | вход быстрее линка — контрактный дроп, подними паузу между датаграммами у источника | | `негабарит` | `-l` (вход) | датаграмма > 1472 Б — уменьшить MTU источника | | `ресинк` | `-d` (выход) | дыра в потоке (радиопотеря сверх FEC) — парсер откатился к следующему валидному magic+hcrc8 | | `bad_crc` | `-d` (выход) | ложный magic в данных, отсеян по hcrc8 — не баг, штатная защита рескана | | `дыр(id)` | `-d` (выход) | сколько id пропущено — оценка объёма потерь на симплексе | `udp_probe send/recv` — тестовый генератор/приёмник UDP для бринг-апа (busybox-прошивка без `nc`/`socat`, Python на хосте запрещён правилом №1): `udp_probe send ip:port РАЗМЕР_ЧАНКА [ПАУЗА_МС]` (stdin → UDP), `udp_probe recv ПОРТ ТАЙМАУТ_ПРОСТОЯ_С` (UDP → stdout, самозавершается по простою — таймер тикает с запуска процесса, не с первой датаграммы).