Files
SDR-Rover/docs/lab046_current_state.md

20 KiB
Raw Blame History

Lab046: текущее состояние

Статус

LAB046 SOFTWARE OBJECT TRANSPORT = COMPLETE.

CONTINUOUS STREAM VALIDATION = PASS (NORMALIZED SOFT-SYMBOL LEVEL). RF TX = 0. CONTINUOUS SAMPLE/IQ VALIDATION = NOT PERFORMED. HARDWARE VALIDATION = NOT PERFORMED.

Lab046 — только программная лаба. HackRF и hackrf_transfer не запускались, эфирной передачи нет. Ни JPEG, ни видео, ни QPSK.

Назначение

Lab046 переходит от одиночной 256-байтной полезной нагрузки к передаче произвольного бинарного объекта последовательностью независимо защищённых радиокадров. Два уровня строго разделены и не смешиваются:

  • Уровень 1 (внутри одного кадра) — Lab045. Свёрточный код R=1/2 K=7, перемежитель, мягкий декодер Витерби, CRC пакета. Исправляет неизвестные битовые ошибки внутри принятого кадра.
  • Уровень 2 (большой объект) — существующий protocol/image_fragments. Фрагментация, нумерация фрагментов, идентификатор объекта, сборка. Собирает объект из нескольких независимо принятых кадров.

Новый код в experiments/lab046_bpsk_object_transport.py минимален: он связывает два готовых уровня и добавляет fail-closed сборщик объекта. Отдельный модуль формата фрагмента НЕ создавался: переиспользован 12-байтовый заголовок image_fragments (>IHHI: object_id u32, fragment_index u16, fragment_count u16, original_size u32).

Аудит: что переиспользовано, чего не хватало, минимальный новый слой

  1. Переиспользовано. protocol/image_fragments уже даёт нужную семантику фрагмента: идентификатор объекта, индекс, число фрагментов, общий размер, а также fail-closed reassemble_image (несовпадение id/count/size, конфликтующие дубликаты, недостающие фрагменты, финальная проверка длины). Слой Lab045 даёт кадр целиком: build_transmission_plan, analyze_post_preamble_soft, simulate_soft_channel, диагностику pre/post-FEC.
  2. Чего не хватало. Не было (а) упаковки фрагмента в 256-байтную нагрузку Lab045 с обрезкой добивки на приёме по длине из заголовка и (б) сборщика, который принимает по одному независимо декодированному кадру и никогда не бросает на плохом входе, возвращая object_complete = False вместо фиктивного объекта.
  3. Минимальный новый слой. recover_fragment_from_payload (fail-closed декод + обрезка добивки) и класс ObjectAssembler (offer/missing/complete/ recovered). Erasure-код 12+3 GF(256) (protocol/packet_erasure_fec) НЕ использован и НЕ изменён — он остаётся будущим расширением для потери целых фрагментов, а не для битовых ошибок (это работа свёрточного кода).

После этого Lab046A закрыла оставшийся пробел: до неё Lab046 передавала каждый кадр отдельным вызовом и выбирала его из TX-плана по известному индексу. Функции поиска нескольких кадров в одном потоке не было. Теперь scan_continuous_soft_stream получает только один массив soft-символов, самостоятельно ищет PRBS7 после монотонного cursor, проверяет защищённый PHY header, вычисляет длину coded body из принятого packet length, выполняет готовые FEC/CRC Lab045 и передаёт принятый fragment в ObjectAssembler.

TX offsets, список длин кадров, ожидаемое число 68, порядок/индексы TX, исходный объект, TX packets и coded bits scanner-у не передаются. TX-side frame_starts остаётся только в генераторе потока и используется тестом для сравнения уже после независимого receive.

Граница доказательства Lab046A

Lab046A работает на уровне одного непрерывного нормализованного soft BPSK stream (float32). Это разрешённый интерфейс после уже проверенного в Lab045 sample-level front-end: PRBS7 detection, matched filtering, timing, CFO и phase/polarity recovery. Внутри нового длинного теста эти sample-level операции заново не моделируются для каждого кадра. Поэтому результат доказывает самостоятельный поиск границ, header-driven длину, FEC/CRC и сборку объекта в непрерывном soft stream, но не является аппаратной или непрерывной IQ-проверкой.

Каждый фрагмент содержит полный активный Lab045 PHY frame: 1280 бит PRBS7, 144 бита защищённого header и 4300 coded bits. Точная полная sample-level форма с guard и хвостом ФРК учитывается как 2 306 400 отсчётов на кадр, однако все 156 835 200 IQ-отсчётов одновременно в памяти не создаются. Это намеренное ограничение памяти, а не использование известных границ приёмником.

Scanner fail-closed:

  • cursor всегда растёт, после принятого кадра переносится за вычисленный конец;
  • все PRBS7 candidates выше порога рассматриваются в порядке stream; после принятого кадра cursor переносится за вычисленный конец и не даёт декодировать этот кадр повторно;
  • плохой header, усечённое body, ошибка FEC/CRC отвергают candidate, но поиск следующего продолжается;
  • MAX_FRAMES и MAX_SEARCH_ITERATIONS дают явную причину остановки, а не частичный объект, объявленный полным;
  • scan продолжается до конца capture и не останавливается на числе 68.

Сценарии continuous stream

Сценарий Получено
Идеальный, кадры подряд candidates 68, valid headers 68, FEC PASS 68, CRC PASS 68/68, fragments 68, complete=True, SHA совпал, 0 байт-ошибок
Фон до/между/после 37/37/43 soft-символа, valid frames 68, complete=True, SHA совпал
Непрерывный AWGN, sigma=0.40, seed=46001 frames 68, pre-FEC 1791, post-FEC 0, CRC 68/0, complete=True, SHA совпал
Valid-looking preamble + bad protected header candidates 71, rejected 3, последующие valid frames 68, complete=True, SHA совпал

Фон между кадрами добавлен только после кадров 7, 23 и 51: 11, 19 и 7 soft-символов, всего 37. В baseline дополнительного inter-frame gap нет. Отдельный тест с валидным header для 12-байтного packet и повреждённым body проверяет, что scanner берёт длину именно из принятого header, отвергает этот candidate и затем находит настоящий кадр.

Verifier нашёл опасный вариант прежнего глобального non-maximum suppression: более сильный ложный PRBS7 peak мог удалить более поздний настоящий старт кадра. Поэтому suppression убран из списка кандидатов, а добавленный регрессионный тест проверяет, что ложный peak не скрывает настоящий кадр.

Память continuous stream

Основной 68-кадровый soft stream содержит 389 232 значения float32 и занимает 1 556 928 байт (1,485 MiB). Массив correlation scores занимает 1 551 812 байт, накопленная энергия float64 — 3 113 864 байта. FFT и denominator создают временное рабочее пространство, размер которого зависит от SciPy, но стратегия остается в пределах десятков MiB, а не нескольких GiB. Для сравнения, один материализованный continuous complex64 IQ stream из 156 835 200 отсчётов занял бы около 1,17 GiB ещё до временных копий.

Детерминированный объект

  • Размер 16384 байта (16 KiB). Это не JPEG, а воспроизводимый псевдослучайный поток numpy.random.default_rng(seed).integers(0,256,...).
  • Выбор размера. 16 KiB — компромисс: достаточно велик, чтобы кадров было много (68) и учёт фрагментации/сборки был содержательным, но достаточно мал, чтобы программный прогон 68 кадров с мягким Витерби считался секунды. Ровный размер (не кратный 244) специально оставляет короткий последний фрагмент.
  • Seed объекта по умолчанию 46, идентификатор объекта 0x4C303436 («L046»).
  • SHA-256 объекта по умолчанию: 748013b5f670c82bd52650be5454555355dbd8528538753df4558f1c3408d515.
  • Объект не сохраняется в Git (крупный генерируемый артефакт); при необходимости он детерминированно восстанавливается по seed.

Раскладка нагрузки пакета

Полезная нагрузка Lab045 остаётся 256 байт (доказанная рабочая точка):

[12 байт метаданных фрагмента][244 байта данных фрагмента]

FRAGMENT_DATA_SIZE = 256 − 12 = 244. Последний фрагмент короче; его нагрузка добивается нулями до 256 байт перед Lab045 и обрезается на приёме по длине, вычисленной только из полей заголовка (expected_fragment_data_length). Для 16384 байт последний, 68-й фрагмент несёт 36 байт данных (16384 − 67·244).

Fail-closed сборка (без оракула)

Приёмник независим: переданный объект и TX-биты не используются ни для приёма пакета, ни для FEC, ни для восстановления метаданных, ни для решения о завершении объекта. Сравнение с оригиналом делается только в диагностике, после независимого CRC-решения и независимой сборки.

ObjectAssembler.offer(fragment) возвращает accepted/duplicate/conflicting/ rejected и никогда не бросает. Отвергаются: чужой object_id, несогласованные count/original_size, невозможный индекс, конфликтующий дубликат (тот же индекс, другие данные), malformed/усечённые метаданные. Идентичный дубликат игнорируется. Пока не собраны все индексы, object_complete() = False, а recovered_object() = None — фиктивный объект не создаётся. Поле original_size проверяется на обе границы ((count−1)·244 < original_size ≤ count·244), чтобы абсурдное значение не управляло размером аллокаций.

Программные сценарии (seed фиксирован)

Сценарий Условие Ожидалось Получено
A. Идеальный канал шума нет объект точен complete=True, sha совпал, 0 байт-ошибок, CRC 68/0
B. Распределённые битовые ошибки гауссов шум sigma=0.40 pre-FEC>0, post-FEC=0, все CRC PASS, объект точен pre-FEC 1805, post-FEC 0, CRC 68/0, complete=True, sha совпал
C. Приём не по порядку обратный порядок кадров объект точен complete=True, sha совпал
D. Дубликаты фрагментов повтор кадров 0,2,2 объект точен, дубликаты учтены duplicates≥1, complete=True, sha совпал
E. Один потерянный фрагмент кадр 1 не дошёл OBJECT COMPLETE = NO complete=False, 1 в missing, recovered SHA = None
F. Повреждённый пакет инверсия coded-потока кадра 0 CRC FAIL, объекта нет crc_fail≥1, complete=False, recovered SHA = None

Сценарий B — честная точка: уровень sigma выбран так, чтобы мягкий Витерби исправлял все 68 кадров (при sigma=0.45+ отдельный кадр не декодируется, и без erasure-FEC объект оказывается неполным — это по сути сценарий E, а не подгон). Числа не подкручивались ради прохождения: до FEC присутствуют 1805 реальных ошибок кодированных бит, после мягкого Витерби — ноль, объект побитово точен.

Метрики (объект 16384 байта, идеальный канал)

Величина Значение
Размер объекта 16384 байта
SHA-256 объекта 748013b5…3408d515
Размер метаданных фрагмента 12 байт/пакет
Размер данных фрагмента 244 байта (последний 36)
Число фрагментов 68
Размер протокольного пакета 268 байт (256 нагрузка + 8 header + 4 CRC)
Число BPSK-кадров 68
Всего информационных BPSK-символов 389 232 (5724 × 68)
Всего отсчётов формы сигнала 156 835 200 (2 306 400 × 68)
Baseline: дополнительный inter-frame gap 0 отсчётов
Baseline: полная длительность 19,604400 с
Baseline: полезный goodput 835,7 байт/с = 6,685846 кбит/с
Фоновый тест: leading/inter-frame/trailing 14 800 / 14 800 / 17 200 отсчётов
Фоновый тест: полная длина и длительность 156 882 000 отсчётов, 19,610250 с
Фоновый тест: фактический goodput 6,683852 кбит/с
Полезных бит на символ ≈ 0,337

Учёт кадра совпадает с Lab045: 2144 информационных бита, 6 tail-бит, 4300 кодированных бит (R=1/2), перемежитель 4300, PHY header 48→144 бит (×3), преамбула PRBS7 1280 бит, итого 5724 информационных символа кадра. Форма сигнала добавляет 42 символо-времени (32 нулевых guard + 10 хвост ФРК, shape_frame_like_lab042, режим full): 5766 символо-времён = 2 306 400 отсчётов = 288,3 мс на кадр при 8 MS/s.

Разбор накладных расходов

  • Фрагментация (уровень 2): 12 байт метаданных на пакет × 68 = 816 байт.
  • Протокольный header+CRC (уровень 1): 12 байт на пакет × 68 = 816 байт.
  • Свёрточный код R=1/2: удваивает число кодированных бит (2144→4300 с tail).
  • PHY преамбула и защищённый header: 1280 + 144 = 1424 бита на кадр.
  • Guard/фильтр: 42 символо-времени на кадр (32 guard + 10 хвост ФРК).
  • Добивка последнего фрагмента: 208 нулевых байт (только 68-й кадр).

Goodput 6,69 кбит/с — это честная baseline при 20 ксимв/с с полной преамбулой и guard на каждый кадр; в реальной серии кадров преамбула на кадр доминирует в накладных расходах, и это направление для будущей оптимизации, а не дефект.

Фактические программные результаты

  • Новый набор tests/test_lab046_bpsk_object_transport.py: все проверки проходят (фрагмент туда-обратно, короткий последний фрагмент, чужой object_id, невозможный индекс, несогласованный счётчик, усечённые и абсурдные метаданные без крупной аллокации, полный тракт PHY для сценариев A–F, отсутствие оракула, последовательный continuous scan, rejected candidate, MAX_FRAMES/MAX_SEARCH_ITERATIONS, регрессия против удаления настоящего кадра ложным PRBS7 peak, детерминизм и SHA объекта).
  • Полный тракт объект → фрагменты → кадр Lab045 → канал → приём → сборка проверен для всех шести сценариев значениями выше.

Software gate

  • python tools/quick_gate.py: PASS; 454 теста ядра, 6 функциональных проверок лабораторий выполнено, 13 штатно пропущено.
  • python -m pytest tests/ -q: 454 passed, 0 failed, 0 errors, 0 skipped. Единственное предупреждение связано с отсутствием прав на создание .pytest_cache; на выполнение и результат тестов оно не повлияло.
  • git diff --check: чист.

Ограничения и следующий шаг

RF TX = 0, аппаратная проверка не проводилась. Erasure-код 12+3 GF(256) не задействован (будущее восстановление потери целых фрагментов). Оптимизация преамбулы/guard на серию кадров, QPSK, JPEG и видео в этой лабе не делались. Следующий этап определяется после анализа (Lab046 первым смотрит ChatGPT).